Repository: ippontech/tatami
Branch: master
Commit: 90fdbfc474dc
Files: 1921
Total size: 12.9 MB
Directory structure:
gitextract_2urwtjut/
├── .gitignore
├── CONTRIBUTING.md
├── README.md
├── etc/
│ └── installation/
│ └── ubuntu/
│ ├── files/
│ │ └── maven/
│ │ └── settings.xml
│ ├── install.sh
│ ├── uninstall.sh
│ └── update.sh
├── jenkinsScripts/
│ ├── insertGoogleAuthKeys.sh
│ ├── restoreDatabase.sh
│ ├── saveDatabase.sh
│ ├── startTatami.sh
│ └── stopTatami.sh
├── mobile/
│ ├── .bowerrc
│ ├── .editorconfig
│ ├── .gitignore
│ ├── README.md
│ ├── bower.json
│ ├── config.xml
│ ├── gulpfile.js
│ ├── hooks/
│ │ ├── README.md
│ │ └── after_prepare/
│ │ └── 010_add_platform_class.js
│ ├── ionic.project
│ ├── karma.ci.conf.js
│ ├── package.json
│ ├── pom.xml
│ ├── resources/
│ │ ├── icon.psd
│ │ └── splash.psd
│ ├── scss/
│ │ └── ionic.app.scss
│ └── www/
│ ├── app/
│ │ ├── components/
│ │ │ ├── follow/
│ │ │ │ ├── follow.html
│ │ │ │ ├── follow.js
│ │ │ │ ├── follower/
│ │ │ │ │ ├── follower.controller.js
│ │ │ │ │ ├── follower.html
│ │ │ │ │ └── follower.js
│ │ │ │ ├── following/
│ │ │ │ │ ├── following.controller.js
│ │ │ │ │ ├── following.html
│ │ │ │ │ └── following.js
│ │ │ │ └── suggested/
│ │ │ │ ├── suggested.controller.js
│ │ │ │ ├── suggested.html
│ │ │ │ └── suggested.js
│ │ │ ├── home/
│ │ │ │ ├── favorites/
│ │ │ │ │ ├── favorites.controller.js
│ │ │ │ │ ├── favorites.html
│ │ │ │ │ └── favorites.js
│ │ │ │ ├── home.html
│ │ │ │ ├── home.js
│ │ │ │ ├── mentions/
│ │ │ │ │ ├── mentions.controller.js
│ │ │ │ │ ├── mentions.html
│ │ │ │ │ └── mentions.js
│ │ │ │ ├── more/
│ │ │ │ │ ├── all_users/
│ │ │ │ │ │ ├── all.users.controller.js
│ │ │ │ │ │ ├── all.users.html
│ │ │ │ │ │ └── all.users.js
│ │ │ │ │ ├── blocked_users/
│ │ │ │ │ │ ├── blocked.users.controller.js
│ │ │ │ │ │ ├── blocked.users.html
│ │ │ │ │ │ └── blocked.users.js
│ │ │ │ │ ├── company/
│ │ │ │ │ │ ├── company-timeline.html
│ │ │ │ │ │ ├── company.timeline.controller.js
│ │ │ │ │ │ └── company.timeline.js
│ │ │ │ │ ├── more.controller.js
│ │ │ │ │ ├── more.html
│ │ │ │ │ ├── more.js
│ │ │ │ │ ├── reportedStatus/
│ │ │ │ │ │ ├── reportedStatus.controller.js
│ │ │ │ │ │ ├── reportedStatus.html
│ │ │ │ │ │ └── reportedStatus.js
│ │ │ │ │ └── settings/
│ │ │ │ │ ├── settings.controller.js
│ │ │ │ │ ├── settings.html
│ │ │ │ │ └── settings.js
│ │ │ │ └── timeline/
│ │ │ │ ├── timeline.controller.js
│ │ │ │ ├── timeline.html
│ │ │ │ └── timeline.js
│ │ │ ├── login/
│ │ │ │ ├── login.controller.js
│ │ │ │ ├── login.html
│ │ │ │ ├── login.js
│ │ │ │ └── server/
│ │ │ │ ├── server.controller.js
│ │ │ │ ├── server.html
│ │ │ │ └── server.js
│ │ │ └── post/
│ │ │ ├── post.controller.js
│ │ │ ├── post.html
│ │ │ ├── post.js
│ │ │ └── postbar.directive.js
│ │ ├── shared/
│ │ │ ├── config/
│ │ │ │ ├── marked.config.js
│ │ │ │ ├── marked.filter.js
│ │ │ │ └── tatami.marked.js
│ │ │ ├── interceptor/
│ │ │ │ └── auth.interceptor.js
│ │ │ ├── providers/
│ │ │ │ ├── provider.js
│ │ │ │ └── tatami.state.provider.js
│ │ │ ├── services/
│ │ │ │ ├── HomeService.js
│ │ │ │ ├── ProfileService.js
│ │ │ │ ├── StatusService.js
│ │ │ │ ├── UserService.js
│ │ │ │ ├── account.service.js
│ │ │ │ ├── block.service.js
│ │ │ │ ├── localStorage.service.js
│ │ │ │ ├── path.service.js
│ │ │ │ ├── report.service.js
│ │ │ │ ├── service.js
│ │ │ │ ├── tag.service.js
│ │ │ │ └── toast.service.js
│ │ │ ├── state/
│ │ │ │ ├── conversation/
│ │ │ │ │ ├── conversation.controller.js
│ │ │ │ │ └── conversation.html
│ │ │ │ ├── profile/
│ │ │ │ │ ├── profile.controller.js
│ │ │ │ │ ├── profile.html
│ │ │ │ │ └── userOptionsMenu.html
│ │ │ │ └── tag/
│ │ │ │ ├── tag.controller.js
│ │ │ │ └── tag.html
│ │ │ ├── status/
│ │ │ │ ├── blockUserMenu.html
│ │ │ │ ├── list/
│ │ │ │ │ ├── status-list.html
│ │ │ │ │ └── status.list.directive.js
│ │ │ │ ├── status.directive.js
│ │ │ │ ├── status.html
│ │ │ │ └── status.refresher.service.js
│ │ │ └── user/
│ │ │ ├── user-detail.html
│ │ │ ├── user.detail.directive.js
│ │ │ ├── user.directive.js
│ │ │ ├── user.html
│ │ │ ├── user.refresher.service.js
│ │ │ ├── users.directive.js
│ │ │ └── users.html
│ │ ├── tatami.controller.js
│ │ ├── tatami.endpoint.js
│ │ ├── tatami.html
│ │ └── tatamiApp.js
│ ├── css/
│ │ ├── ionic.app.css
│ │ └── style.css
│ ├── i18n/
│ │ ├── en/
│ │ │ ├── conversation.json
│ │ │ ├── follow.json
│ │ │ ├── home.json
│ │ │ ├── login.json
│ │ │ ├── more.json
│ │ │ ├── post.json
│ │ │ ├── server.json
│ │ │ ├── status.json
│ │ │ └── user.json
│ │ └── fr/
│ │ ├── conversation.json
│ │ ├── follow.json
│ │ ├── home.json
│ │ ├── login.json
│ │ ├── more.json
│ │ ├── post.json
│ │ ├── server.json
│ │ ├── status.json
│ │ └── user.json
│ ├── index.html
│ └── test/
│ └── javascript/
│ └── components/
│ └── profile/
│ └── profile.controller.spec.js
├── pom.xml
├── scripts/
│ └── insertBuildVersion.sh
├── services/
│ ├── pom.xml
│ └── src/
│ ├── main/
│ │ ├── java/
│ │ │ ├── fr/
│ │ │ │ └── ippon/
│ │ │ │ └── tatami/
│ │ │ │ ├── config/
│ │ │ │ │ ├── ApplicationConfiguration.java
│ │ │ │ │ ├── AsyncConfiguration.java
│ │ │ │ │ ├── CacheConfiguration.java
│ │ │ │ │ ├── CassandraConfiguration.java
│ │ │ │ │ ├── ColumnFamilyKeys.java
│ │ │ │ │ ├── Constants.java
│ │ │ │ │ ├── DispatcherServletConfig.java
│ │ │ │ │ ├── GroupRoles.java
│ │ │ │ │ ├── MailConfiguration.java
│ │ │ │ │ ├── MetricsConfiguration.java
│ │ │ │ │ ├── SearchConfiguration.java
│ │ │ │ │ └── metrics/
│ │ │ │ │ ├── CassandraHealthCheck.java
│ │ │ │ │ └── JavaMailHealthCheck.java
│ │ │ │ ├── domain/
│ │ │ │ │ ├── Attachment.java
│ │ │ │ │ ├── Avatar.java
│ │ │ │ │ ├── DigestType.java
│ │ │ │ │ ├── Domain.java
│ │ │ │ │ ├── DomainConfiguration.java
│ │ │ │ │ ├── Group.java
│ │ │ │ │ ├── User.java
│ │ │ │ │ ├── UserStatusStat.java
│ │ │ │ │ ├── status/
│ │ │ │ │ │ ├── AbstractStatus.java
│ │ │ │ │ │ ├── Announcement.java
│ │ │ │ │ │ ├── MentionFriend.java
│ │ │ │ │ │ ├── MentionShare.java
│ │ │ │ │ │ ├── Share.java
│ │ │ │ │ │ ├── Status.java
│ │ │ │ │ │ ├── StatusDetails.java
│ │ │ │ │ │ └── StatusType.java
│ │ │ │ │ └── validation/
│ │ │ │ │ ├── ContraintsAttachmentCreation.java
│ │ │ │ │ └── ContraintsUserCreation.java
│ │ │ │ ├── repository/
│ │ │ │ │ ├── AppleDeviceRepository.java
│ │ │ │ │ ├── AppleDeviceUserRepository.java
│ │ │ │ │ ├── AttachmentRepository.java
│ │ │ │ │ ├── AvatarRepository.java
│ │ │ │ │ ├── BlockRepository.java
│ │ │ │ │ ├── CounterRepository.java
│ │ │ │ │ ├── DaylineRepository.java
│ │ │ │ │ ├── DiscussionRepository.java
│ │ │ │ │ ├── DomainConfigurationRepository.java
│ │ │ │ │ ├── DomainRepository.java
│ │ │ │ │ ├── DomainlineRepository.java
│ │ │ │ │ ├── FavoritelineRepository.java
│ │ │ │ │ ├── FollowerRepository.java
│ │ │ │ │ ├── FriendRepository.java
│ │ │ │ │ ├── GroupCounterRepository.java
│ │ │ │ │ ├── GroupDetailsRepository.java
│ │ │ │ │ ├── GroupMembersRepository.java
│ │ │ │ │ ├── GroupRepository.java
│ │ │ │ │ ├── GrouplineRepository.java
│ │ │ │ │ ├── IdempotentRepository.java
│ │ │ │ │ ├── MailDigestRepository.java
│ │ │ │ │ ├── MentionlineRepository.java
│ │ │ │ │ ├── RegistrationRepository.java
│ │ │ │ │ ├── ResolvedReportRepository.java
│ │ │ │ │ ├── RssUidRepository.java
│ │ │ │ │ ├── SharesRepository.java
│ │ │ │ │ ├── StatusAttachmentRepository.java
│ │ │ │ │ ├── StatusReportRepository.java
│ │ │ │ │ ├── StatusRepository.java
│ │ │ │ │ ├── TagCounterRepository.java
│ │ │ │ │ ├── TagFollowerRepository.java
│ │ │ │ │ ├── TaglineRepository.java
│ │ │ │ │ ├── TimelineRepository.java
│ │ │ │ │ ├── TrendRepository.java
│ │ │ │ │ ├── UserAttachmentRepository.java
│ │ │ │ │ ├── UserGroupRepository.java
│ │ │ │ │ ├── UserRepository.java
│ │ │ │ │ ├── UserTagRepository.java
│ │ │ │ │ ├── UserTrendRepository.java
│ │ │ │ │ ├── UserlineRepository.java
│ │ │ │ │ └── cassandra/
│ │ │ │ │ ├── AbstractCassandraFollowerRepository.java
│ │ │ │ │ ├── AbstractCassandraFriendRepository.java
│ │ │ │ │ ├── AbstractCassandraLineRepository.java
│ │ │ │ │ ├── CassandraAppleDeviceRepository.java
│ │ │ │ │ ├── CassandraAppleDeviceUserRepository.java
│ │ │ │ │ ├── CassandraAttachmentRepository.java
│ │ │ │ │ ├── CassandraAvatarRepository.java
│ │ │ │ │ ├── CassandraBlockRepository.java
│ │ │ │ │ ├── CassandraCounterRepository.java
│ │ │ │ │ ├── CassandraDaylineRepository.java
│ │ │ │ │ ├── CassandraDiscussionRepository.java
│ │ │ │ │ ├── CassandraDomainConfigurationRepository.java
│ │ │ │ │ ├── CassandraDomainRepository.java
│ │ │ │ │ ├── CassandraDomainlineRepository.java
│ │ │ │ │ ├── CassandraFavoritelineRepository.java
│ │ │ │ │ ├── CassandraFollowerRepository.java
│ │ │ │ │ ├── CassandraFriendRepository.java
│ │ │ │ │ ├── CassandraGroupCounterRepository.java
│ │ │ │ │ ├── CassandraGroupDetailsRepository.java
│ │ │ │ │ ├── CassandraGroupMembersRepository.java
│ │ │ │ │ ├── CassandraGroupRepository.java
│ │ │ │ │ ├── CassandraGrouplineRepository.java
│ │ │ │ │ ├── CassandraIdempotentRepository.java
│ │ │ │ │ ├── CassandraMailDigestRepository.java
│ │ │ │ │ ├── CassandraMentionlineRepository.java
│ │ │ │ │ ├── CassandraRegistrationRepository.java
│ │ │ │ │ ├── CassandraRssUidRepository.java
│ │ │ │ │ ├── CassandraSharesRepository.java
│ │ │ │ │ ├── CassandraStatusAttachmentRepository.java
│ │ │ │ │ ├── CassandraStatusReportRepository.java
│ │ │ │ │ ├── CassandraStatusRepository.java
│ │ │ │ │ ├── CassandraTagCounterRepository.java
│ │ │ │ │ ├── CassandraTagFollowerRepository.java
│ │ │ │ │ ├── CassandraTaglineRepository.java
│ │ │ │ │ ├── CassandraTimelineRepository.java
│ │ │ │ │ ├── CassandraTrendRepository.java
│ │ │ │ │ ├── CassandraUserAttachmentRepository.java
│ │ │ │ │ ├── CassandraUserGroupRepository.java
│ │ │ │ │ ├── CassandraUserRepository.java
│ │ │ │ │ ├── CassandraUserTagRepository.java
│ │ │ │ │ ├── CassandraUserTrendRepository.java
│ │ │ │ │ └── CassandraUserlineRepository.java
│ │ │ │ ├── security/
│ │ │ │ │ ├── AjaxAuthenticationFailureHandler.java
│ │ │ │ │ ├── AjaxAuthenticationSuccessHandler.java
│ │ │ │ │ ├── AjaxLogoutSuccessHandler.java
│ │ │ │ │ ├── AuthenticationService.java
│ │ │ │ │ ├── DomainViolationException.java
│ │ │ │ │ ├── GoogleApiAuthenticationProvider.java
│ │ │ │ │ ├── GoogleAuthenticationProvider.java
│ │ │ │ │ ├── GoogleAuthenticationToken.java
│ │ │ │ │ ├── GoogleAutoRegisteringUserDetailsService.java
│ │ │ │ │ ├── Http401UnauthorizedEntryPoint.java
│ │ │ │ │ ├── OpenIdAutoRegisteringUserDetailsService.java
│ │ │ │ │ ├── TatamiAuthenticationSuccessHandler.java
│ │ │ │ │ ├── TatamiLdapAuthenticationProvider.java
│ │ │ │ │ ├── TatamiUserDetailsService.java
│ │ │ │ │ └── xauth/
│ │ │ │ │ ├── Token.java
│ │ │ │ │ ├── TokenProvider.java
│ │ │ │ │ └── XAuthTokenFilter.java
│ │ │ │ ├── service/
│ │ │ │ │ ├── AdminService.java
│ │ │ │ │ ├── ApplePushService.java
│ │ │ │ │ ├── AtmosphereService.java
│ │ │ │ │ ├── AttachmentService.java
│ │ │ │ │ ├── AvatarService.java
│ │ │ │ │ ├── BlockService.java
│ │ │ │ │ ├── CounterService.java
│ │ │ │ │ ├── FriendshipService.java
│ │ │ │ │ ├── GroupService.java
│ │ │ │ │ ├── MailDigestService.java
│ │ │ │ │ ├── MailService.java
│ │ │ │ │ ├── MentionService.java
│ │ │ │ │ ├── SearchService.java
│ │ │ │ │ ├── StatsService.java
│ │ │ │ │ ├── StatusUpdateService.java
│ │ │ │ │ ├── SuggestionService.java
│ │ │ │ │ ├── TagMembershipService.java
│ │ │ │ │ ├── TimelineService.java
│ │ │ │ │ ├── TrendService.java
│ │ │ │ │ ├── UserService.java
│ │ │ │ │ ├── dto/
│ │ │ │ │ │ ├── StatusDTO.java
│ │ │ │ │ │ ├── UserDTO.java
│ │ │ │ │ │ └── UserGroupDTO.java
│ │ │ │ │ ├── elasticsearch/
│ │ │ │ │ │ ├── ElasticsearchEngine.java
│ │ │ │ │ │ ├── ElasticsearchSearchService.java
│ │ │ │ │ │ ├── EmbeddedElasticsearchEngine.java
│ │ │ │ │ │ └── RemoteElasticsearchEngine.java
│ │ │ │ │ ├── exception/
│ │ │ │ │ │ ├── ArchivedGroupException.java
│ │ │ │ │ │ ├── ReplyStatusException.java
│ │ │ │ │ │ └── StorageSizeException.java
│ │ │ │ │ └── util/
│ │ │ │ │ ├── AnalysisUtil.java
│ │ │ │ │ ├── DomainUtil.java
│ │ │ │ │ ├── RandomUtil.java
│ │ │ │ │ └── ValueComparator.java
│ │ │ │ └── web/
│ │ │ │ ├── atmosphere/
│ │ │ │ │ └── TatamiNotification.java
│ │ │ │ ├── rest/
│ │ │ │ │ └── dto/
│ │ │ │ │ ├── ActionStatus.java
│ │ │ │ │ ├── EmailAndUsername.java
│ │ │ │ │ ├── Preferences.java
│ │ │ │ │ ├── Reply.java
│ │ │ │ │ ├── SearchResults.java
│ │ │ │ │ ├── Tag.java
│ │ │ │ │ ├── Trend.java
│ │ │ │ │ ├── UserActionStatus.java
│ │ │ │ │ └── UserPassword.java
│ │ │ │ └── syndic/
│ │ │ │ ├── SyndicTimelineController.java
│ │ │ │ ├── SyndicView.java
│ │ │ │ └── UnknownRssChannelException.java
│ │ │ └── me/
│ │ │ └── prettyprint/
│ │ │ └── hom/
│ │ │ └── CassandraPersistenceProvider.java
│ │ ├── resources/
│ │ │ ├── META-INF/
│ │ │ │ ├── elasticsearch/
│ │ │ │ │ ├── elasticsearch-embedded.yml
│ │ │ │ │ ├── index/
│ │ │ │ │ │ ├── group.json
│ │ │ │ │ │ ├── status.json
│ │ │ │ │ │ └── user.json
│ │ │ │ │ └── logging.yml
│ │ │ │ ├── spring/
│ │ │ │ │ ├── applicationContext-metrics.xml
│ │ │ │ │ └── applicationContext-security.xml
│ │ │ │ └── tatami/
│ │ │ │ ├── customization.properties
│ │ │ │ ├── mails/
│ │ │ │ │ ├── common
│ │ │ │ │ ├── dailyDigestEmail
│ │ │ │ │ ├── deactivatedUserEmail
│ │ │ │ │ ├── invitationMessageEmail
│ │ │ │ │ ├── lostPasswordEmail
│ │ │ │ │ ├── messages/
│ │ │ │ │ │ ├── messages_en.properties
│ │ │ │ │ │ └── messages_fr.properties
│ │ │ │ │ ├── passwordReinitializedEmail
│ │ │ │ │ ├── registrationEmail
│ │ │ │ │ ├── reportedStatusEmail
│ │ │ │ │ ├── userMentionEmail
│ │ │ │ │ ├── userPrivateMessageEmail
│ │ │ │ │ ├── validationEmail
│ │ │ │ │ └── weeklyDigestEmail
│ │ │ │ └── tatami.properties
│ │ │ ├── ehcache.xml
│ │ │ └── logback.xml
│ │ └── webapp/
│ │ └── app/
│ │ └── shared/
│ │ └── services/
│ │ ├── AuthenticationService.js
│ │ ├── GeolocService.js
│ │ ├── GroupService.js
│ │ ├── HomeService.js
│ │ ├── ProfileService.js
│ │ ├── SearchService.js
│ │ ├── StatusService.js
│ │ ├── TagService.js
│ │ ├── TopPostersService.js
│ │ ├── UserService.js
│ │ └── UserSession.js
│ └── test/
│ ├── java/
│ │ └── fr/
│ │ └── ippon/
│ │ └── tatami/
│ │ ├── AbstractCassandraTatamiTest.java
│ │ ├── repository/
│ │ │ ├── MailDigestRepositoryTest.java
│ │ │ ├── StatusReportRepositoryTest.java
│ │ │ ├── StatusRepositoryTest.java
│ │ │ ├── TagFollowerRepositoryTest.java
│ │ │ └── UserRepositoryTest.java
│ │ ├── service/
│ │ │ ├── FriendshipServiceTest.java
│ │ │ ├── GroupServiceTest.java
│ │ │ ├── MailDigestServiceTest.java
│ │ │ ├── StatsServiceTest.java
│ │ │ ├── StatusDeletionTest.java
│ │ │ ├── StatusUpdateServiceTest.java
│ │ │ ├── TagMembershipServiceTest.java
│ │ │ ├── TimelineServiceTest.java
│ │ │ ├── TrendServiceTest.java
│ │ │ ├── UserServiceTest.java
│ │ │ └── elasticsearch/
│ │ │ └── ElasticsearchSearchServiceTest.java
│ │ ├── test/
│ │ │ ├── MockUtils.java
│ │ │ └── application/
│ │ │ ├── ApplicationTestConfiguration.java
│ │ │ └── WebApplicationTestConfiguration.java
│ │ └── web/
│ │ └── syndic/
│ │ └── SyndicTimelineControllerTest.java
│ ├── jmeter/
│ │ ├── tatami-create-users.jmx
│ │ └── tatami-stress-test.jmx
│ └── resources/
│ ├── dataset/
│ │ └── dataset.json
│ ├── logback.xml
│ └── tatami/
│ └── tatami-test.properties
├── src/
│ ├── integration/
│ │ ├── java/
│ │ │ ├── fr/
│ │ │ │ └── ippon/
│ │ │ │ └── tatami/
│ │ │ │ ├── test/
│ │ │ │ │ └── support/
│ │ │ │ │ ├── LdapTestServer.java
│ │ │ │ │ └── LdapTestServerJunitLauncher.java
│ │ │ │ └── uitest/
│ │ │ │ ├── AuthenticationSpec.groovy
│ │ │ │ ├── NewRegistrationSpec.groovy
│ │ │ │ └── support/
│ │ │ │ ├── AccountUtils.groovy
│ │ │ │ ├── CassandraAccessUtils.groovy
│ │ │ │ ├── RegistrationUtils.groovy
│ │ │ │ └── TatamiBaseGebSpec.groovy
│ │ │ └── pages/
│ │ │ ├── EmailVerifiedPage.groovy
│ │ │ ├── HomePage.groovy
│ │ │ ├── LoginPage.groovy
│ │ │ ├── TatamiBasePage.groovy
│ │ │ └── google/
│ │ │ ├── GoogleAuthenticationPage.groovy
│ │ │ └── GoogleOpenIdPage.groovy
│ │ └── resources/
│ │ ├── GebConfig.groovy
│ │ └── fr/
│ │ └── ippon/
│ │ └── tatami/
│ │ └── test/
│ │ └── support/
│ │ └── ipponTestLdapExport.ldif
│ └── main/
│ └── cql/
│ ├── install.cql
│ └── upgrade/
│ ├── upgrade_from_1.0.27_to_2.0.0.cql
│ ├── upgrade_from_2.0.0_to_2.1.0.cql
│ ├── upgrade_from_2.1.3_to_2.2.0.cql
│ └── upgrade_from_3.0.26_to_3.0.27.cql
├── tatamibot/
│ ├── pom.xml
│ └── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── fr/
│ │ │ └── ippon/
│ │ │ └── tatami/
│ │ │ ├── bot/
│ │ │ │ ├── Tatamibot.java
│ │ │ │ ├── config/
│ │ │ │ │ └── TatamibotConfiguration.java
│ │ │ │ ├── processor/
│ │ │ │ │ ├── LastUpdateDateTatamibotConfigurationUpdater.java
│ │ │ │ │ └── TatamiStatusProcessor.java
│ │ │ │ └── route/
│ │ │ │ ├── CommonRouteBuilder.java
│ │ │ │ ├── GitHubRouteBuilder.java
│ │ │ │ ├── RssRouteBuilder.java
│ │ │ │ ├── SourceRouteBuilderBase.java
│ │ │ │ └── TwitterRouteBuilder.java
│ │ │ ├── repository/
│ │ │ │ ├── TatamibotConfigurationRepository.java
│ │ │ │ └── cassandra/
│ │ │ │ └── CassandraTatamibotConfigurationRepository.java
│ │ │ └── web/
│ │ │ └── bot/
│ │ │ └── TatamibotController.java
│ │ └── resources/
│ │ └── META-INF/
│ │ └── spring/
│ │ └── applicationContext-tatamibot.xml
│ └── test/
│ ├── java/
│ │ └── fr/
│ │ └── ippon/
│ │ └── tatami/
│ │ ├── bot/
│ │ │ ├── TatamibotTest.java
│ │ │ └── route/
│ │ │ ├── CommonRouteBuilderTest.java
│ │ │ ├── RssRouteBuilderCamelTest.java
│ │ │ ├── RssRouteBuilderUnitTest.java
│ │ │ ├── SourceRouteBuilderBaseCamelTest.java
│ │ │ └── TwitterRouteBuilderCamelTest.java
│ │ └── test/
│ │ └── MockUtils.java
│ └── resources/
│ └── fr/
│ └── ippon/
│ └── tatami/
│ └── bot/
│ └── route/
│ └── rss.xml
└── web/
├── gruntfile.js
├── karma.ci.conf.js
├── karma.conf.js
├── package.json
├── pom.xml
└── src/
├── main/
│ ├── java/
│ │ └── fr/
│ │ └── ippon/
│ │ └── tatami/
│ │ └── web/
│ │ ├── atmosphere/
│ │ │ └── RealtimeService.java
│ │ ├── controller/
│ │ │ ├── ErrorController.java
│ │ │ ├── HomeController.java
│ │ │ └── Pac4JSecurityCheckController.java
│ │ ├── fileupload/
│ │ │ ├── FileController.java
│ │ │ ├── Message.java
│ │ │ ├── StatusResponse.java
│ │ │ └── UploadedFile.java
│ │ ├── filter/
│ │ │ ├── IeRefreshWrapper.java
│ │ │ └── TatamiGzipFilter.java
│ │ ├── init/
│ │ │ └── WebConfigurer.java
│ │ └── rest/
│ │ ├── AccountController.java
│ │ ├── AttachmentController.java
│ │ ├── BlockController.java
│ │ ├── CompanyWallController.java
│ │ ├── FavoritesController.java
│ │ ├── FriendshipController.java
│ │ ├── GroupController.java
│ │ ├── MentionsController.java
│ │ ├── SearchController.java
│ │ ├── StatsController.java
│ │ ├── TagController.java
│ │ ├── TimelineController.java
│ │ ├── TrendController.java
│ │ ├── UserController.java
│ │ └── UserXAuthController.java
│ └── webapp/
│ ├── .bowerrc
│ ├── WEB-INF/
│ │ ├── messages/
│ │ │ ├── messages_en.properties
│ │ │ └── messages_fr.properties
│ │ ├── pages/
│ │ │ ├── errors/
│ │ │ │ ├── 404.jsp
│ │ │ │ ├── 500.jsp
│ │ │ │ └── file_not_found.jsp
│ │ │ ├── home.jsp
│ │ │ ├── includes/
│ │ │ │ ├── footer.jsp
│ │ │ │ ├── header.jsp
│ │ │ │ ├── help-home.jsp
│ │ │ │ ├── navigation-admin.jsp
│ │ │ │ ├── template-search-engine.jsp
│ │ │ │ ├── templates-admin.jsp
│ │ │ │ ├── templates.jsp
│ │ │ │ ├── topavatar.jsp
│ │ │ │ └── topmenu.jsp
│ │ │ └── login.jsp
│ │ └── web.xml
│ ├── app/
│ │ ├── TatamiApp.js
│ │ ├── components/
│ │ │ ├── about/
│ │ │ │ ├── AboutModule.js
│ │ │ │ ├── AboutView.html
│ │ │ │ ├── license/
│ │ │ │ │ ├── LicenseController.js
│ │ │ │ │ └── LicenseView.html
│ │ │ │ ├── presentation/
│ │ │ │ │ └── PresentationView.html
│ │ │ │ └── tos/
│ │ │ │ └── ToSView.html
│ │ │ ├── account/
│ │ │ │ ├── AccountController.js
│ │ │ │ ├── AccountModule.js
│ │ │ │ ├── AccountView.html
│ │ │ │ ├── FormController.js
│ │ │ │ ├── FormView.html
│ │ │ │ ├── files/
│ │ │ │ │ ├── FilesController.js
│ │ │ │ │ ├── FilesModule.js
│ │ │ │ │ ├── FilesService.js
│ │ │ │ │ └── FilesView.html
│ │ │ │ ├── groups/
│ │ │ │ │ ├── GroupsController.js
│ │ │ │ │ ├── GroupsModule.js
│ │ │ │ │ ├── GroupsView.html
│ │ │ │ │ ├── creation/
│ │ │ │ │ │ ├── GroupsCreateController.js
│ │ │ │ │ │ └── GroupsCreateView.html
│ │ │ │ │ ├── list/
│ │ │ │ │ │ ├── GroupListController.js
│ │ │ │ │ │ └── GroupsListView.html
│ │ │ │ │ └── manage/
│ │ │ │ │ ├── GroupsManageController.js
│ │ │ │ │ └── GroupsManageView.html
│ │ │ │ ├── password/
│ │ │ │ │ ├── PasswordController.js
│ │ │ │ │ ├── PasswordModule.js
│ │ │ │ │ ├── PasswordService.js
│ │ │ │ │ └── PasswordView.html
│ │ │ │ ├── preferences/
│ │ │ │ │ ├── PreferencesController.js
│ │ │ │ │ ├── PreferencesModule.js
│ │ │ │ │ ├── PreferencesService.js
│ │ │ │ │ └── PreferencesView.html
│ │ │ │ ├── profile/
│ │ │ │ │ ├── ProfileController.js
│ │ │ │ │ ├── ProfileModule.js
│ │ │ │ │ └── ProfileView.html
│ │ │ │ ├── tags/
│ │ │ │ │ ├── TagsController.js
│ │ │ │ │ ├── TagsModule.js
│ │ │ │ │ └── TagsView.html
│ │ │ │ ├── topPosters/
│ │ │ │ │ ├── TopPostersController.js
│ │ │ │ │ ├── TopPostersModule.js
│ │ │ │ │ └── TopPostersView.html
│ │ │ │ └── users/
│ │ │ │ ├── UsersController.js
│ │ │ │ ├── UsersModule.js
│ │ │ │ ├── UsersView.html
│ │ │ │ └── directives/
│ │ │ │ ├── UserAccountController.js
│ │ │ │ ├── UserAccountDirective.js
│ │ │ │ └── UserAccountView.html
│ │ │ ├── admin/
│ │ │ │ ├── AdminController.js
│ │ │ │ ├── AdminModule.js
│ │ │ │ ├── AdminService.js
│ │ │ │ └── AdminView.html
│ │ │ ├── home/
│ │ │ │ ├── HomeModule.js
│ │ │ │ ├── HomeView.html
│ │ │ │ ├── group/
│ │ │ │ │ ├── GroupHeaderController.js
│ │ │ │ │ └── GroupHeaderView.html
│ │ │ │ ├── profile/
│ │ │ │ │ ├── ProfileHeaderController.js
│ │ │ │ │ └── ProfileHeaderView.html
│ │ │ │ ├── search/
│ │ │ │ │ ├── SearchHeaderController.js
│ │ │ │ │ └── SearchHeaderView.html
│ │ │ │ ├── status/
│ │ │ │ │ ├── StatusController.js
│ │ │ │ │ └── StatusView.html
│ │ │ │ ├── tag/
│ │ │ │ │ ├── TagHeaderController.js
│ │ │ │ │ └── TagHeaderView.html
│ │ │ │ ├── timeline/
│ │ │ │ │ └── TimelineHeaderView.html
│ │ │ │ └── welcome/
│ │ │ │ ├── WelcomeController.js
│ │ │ │ └── WelcomeView.html
│ │ │ └── login/
│ │ │ ├── LoginController.js
│ │ │ ├── LoginModule.js
│ │ │ ├── LoginView.html
│ │ │ ├── RegistrationService.js
│ │ │ ├── email/
│ │ │ │ ├── EmailRegistration.html
│ │ │ │ └── EmailRegistrationController.js
│ │ │ ├── google/
│ │ │ │ ├── GoogleLoginController.js
│ │ │ │ └── GoogleLoginView.html
│ │ │ ├── manual/
│ │ │ │ ├── ManualLoginController.js
│ │ │ │ └── ManualLoginView.html
│ │ │ ├── recoverPassword/
│ │ │ │ ├── RecoverPasswordController.js
│ │ │ │ └── RecoverPasswordView.html
│ │ │ └── register/
│ │ │ ├── RegisterController.js
│ │ │ └── RegisterView.html
│ │ └── shared/
│ │ ├── configs/
│ │ │ ├── MarkedConfig.js
│ │ │ ├── MomentConfig.js
│ │ │ └── TranslateConfig.js
│ │ ├── error/
│ │ │ ├── 404View.html
│ │ │ └── 500View.html
│ │ ├── filters/
│ │ │ ├── EmoticonFilter.js
│ │ │ ├── MarkdownFilter.js
│ │ │ └── PlaceholderFilter.js
│ │ ├── footer/
│ │ │ ├── FooterController.js
│ │ │ ├── FooterModule.js
│ │ │ └── FooterView.html
│ │ ├── lists/
│ │ │ ├── status/
│ │ │ │ ├── withContext/
│ │ │ │ │ ├── StatusListContextController.js
│ │ │ │ │ └── StatusListContextView.html
│ │ │ │ └── withoutContext/
│ │ │ │ ├── StatusListController.js
│ │ │ │ └── StatusListView.html
│ │ │ └── user/
│ │ │ ├── UserListController.js
│ │ │ └── UserListView.html
│ │ ├── services/
│ │ │ ├── AuthenticationService.js
│ │ │ ├── GeolocService.js
│ │ │ ├── GroupService.js
│ │ │ ├── HomeService.js
│ │ │ ├── ProfileService.js
│ │ │ ├── SearchService.js
│ │ │ ├── StatusService.js
│ │ │ ├── TagService.js
│ │ │ ├── TopPostersService.js
│ │ │ ├── UserService.js
│ │ │ └── UserSession.js
│ │ ├── sidebars/
│ │ │ ├── home/
│ │ │ │ ├── HomeSidebarController.js
│ │ │ │ ├── HomeSidebarModule.js
│ │ │ │ └── HomeSidebarView.html
│ │ │ └── profile/
│ │ │ ├── ProfileSidebarController.js
│ │ │ ├── ProfileSidebarModule.js
│ │ │ └── ProfileSidebarView.html
│ │ └── topMenu/
│ │ ├── SearchView.html
│ │ ├── TopMenuController.js
│ │ ├── TopMenuModule.js
│ │ ├── TopMenuView.html
│ │ └── post/
│ │ ├── DropdownTagTemplate.html
│ │ ├── DropdownUserTemplate.html
│ │ ├── PostController.js
│ │ ├── PostModule.js
│ │ └── PostView.html
│ ├── assets/
│ │ ├── bower_components/
│ │ │ ├── angular/
│ │ │ │ ├── .bower.json
│ │ │ │ ├── README.md
│ │ │ │ ├── angular-csp.css
│ │ │ │ ├── angular.min.js.gzip
│ │ │ │ ├── bower.json
│ │ │ │ ├── index.js
│ │ │ │ └── package.json
│ │ │ ├── angular-animate/
│ │ │ │ ├── .bower.json
│ │ │ │ ├── README.md
│ │ │ │ ├── bower.json
│ │ │ │ ├── index.js
│ │ │ │ └── package.json
│ │ │ ├── angular-bootstrap/
│ │ │ │ ├── .bower.json
│ │ │ │ └── bower.json
│ │ │ ├── angular-bootstrap-tour/
│ │ │ │ ├── .bower.json
│ │ │ │ ├── README.md
│ │ │ │ ├── app/
│ │ │ │ │ ├── angular-bootstrap-tour.js
│ │ │ │ │ ├── tour_config_provider.js
│ │ │ │ │ ├── tour_controller.js
│ │ │ │ │ ├── tour_directive.js
│ │ │ │ │ ├── tour_helpers.js
│ │ │ │ │ └── tour_step_directive.js
│ │ │ │ ├── bower.json
│ │ │ │ ├── demo/
│ │ │ │ │ └── angular-bootstrap-tour.js
│ │ │ │ ├── dist/
│ │ │ │ │ └── angular-bootstrap-tour.js
│ │ │ │ ├── gruntfile.js
│ │ │ │ ├── karma.conf.js
│ │ │ │ └── test/
│ │ │ │ └── spec/
│ │ │ │ └── angular-bootstrap-tour.spec.js
│ │ │ ├── angular-cookies/
│ │ │ │ ├── .bower.json
│ │ │ │ ├── README.md
│ │ │ │ ├── bower.json
│ │ │ │ ├── index.js
│ │ │ │ └── package.json
│ │ │ ├── angular-mocks/
│ │ │ │ ├── .bower.json
│ │ │ │ ├── README.md
│ │ │ │ ├── angular-mocks.js
│ │ │ │ ├── bower.json
│ │ │ │ ├── ngAnimateMock.js
│ │ │ │ ├── ngMock.js
│ │ │ │ ├── ngMockE2E.js
│ │ │ │ └── package.json
│ │ │ ├── angular-moment/
│ │ │ │ ├── .bower.json
│ │ │ │ ├── .editorconfig
│ │ │ │ ├── .gitignore
│ │ │ │ ├── .jshintrc
│ │ │ │ ├── .npmignore
│ │ │ │ ├── .travis.yml
│ │ │ │ ├── CHANGELOG.md
│ │ │ │ ├── CONTRIBUTING.md
│ │ │ │ ├── Gruntfile.js
│ │ │ │ ├── LICENSE
│ │ │ │ ├── README.md
│ │ │ │ ├── angular-moment.nuspec
│ │ │ │ ├── bower.json
│ │ │ │ ├── karma.conf.js
│ │ │ │ ├── package.json
│ │ │ │ └── tests.js
│ │ │ ├── angular-resource/
│ │ │ │ ├── .bower.json
│ │ │ │ ├── README.md
│ │ │ │ ├── bower.json
│ │ │ │ ├── index.js
│ │ │ │ └── package.json
│ │ │ ├── angular-route/
│ │ │ │ ├── .bower.json
│ │ │ │ ├── README.md
│ │ │ │ ├── angular-route.js
│ │ │ │ └── bower.json
│ │ │ ├── angular-sanitize/
│ │ │ │ ├── .bower.json
│ │ │ │ ├── README.md
│ │ │ │ ├── bower.json
│ │ │ │ ├── index.js
│ │ │ │ └── package.json
│ │ │ ├── angular-touch/
│ │ │ │ ├── .bower.json
│ │ │ │ ├── README.md
│ │ │ │ ├── bower.json
│ │ │ │ ├── index.js
│ │ │ │ └── package.json
│ │ │ ├── angular-translate/
│ │ │ │ ├── .bower.json
│ │ │ │ ├── README.md
│ │ │ │ └── bower.json
│ │ │ ├── angular-translate-storage-cookie/
│ │ │ │ ├── .bower.json
│ │ │ │ ├── README.md
│ │ │ │ └── bower.json
│ │ │ ├── angular-ui-router/
│ │ │ │ ├── .bower.json
│ │ │ │ ├── CHANGELOG.md
│ │ │ │ ├── CONTRIBUTING.md
│ │ │ │ ├── LICENSE
│ │ │ │ ├── README.md
│ │ │ │ ├── api/
│ │ │ │ │ └── angular-ui-router.d.ts
│ │ │ │ ├── bower.json
│ │ │ │ ├── release/
│ │ │ │ │ └── angular-ui-router.js
│ │ │ │ └── src/
│ │ │ │ ├── common.js
│ │ │ │ ├── resolve.js
│ │ │ │ ├── state.js
│ │ │ │ ├── stateDirectives.js
│ │ │ │ ├── stateFilters.js
│ │ │ │ ├── templateFactory.js
│ │ │ │ ├── urlMatcherFactory.js
│ │ │ │ ├── urlRouter.js
│ │ │ │ ├── view.js
│ │ │ │ ├── viewDirective.js
│ │ │ │ └── viewScroll.js
│ │ │ ├── bootstrap/
│ │ │ │ ├── .bower.json
│ │ │ │ ├── DOCS-LICENSE
│ │ │ │ ├── LICENSE
│ │ │ │ ├── LICENSE-MIT
│ │ │ │ ├── README.md
│ │ │ │ ├── bower.json
│ │ │ │ ├── dist/
│ │ │ │ │ ├── css/
│ │ │ │ │ │ ├── bootstrap-theme.css
│ │ │ │ │ │ └── bootstrap.css
│ │ │ │ │ └── js/
│ │ │ │ │ └── bootstrap.js
│ │ │ │ ├── js/
│ │ │ │ │ ├── affix.js
│ │ │ │ │ ├── alert.js
│ │ │ │ │ ├── button.js
│ │ │ │ │ ├── carousel.js
│ │ │ │ │ ├── collapse.js
│ │ │ │ │ ├── dropdown.js
│ │ │ │ │ ├── modal.js
│ │ │ │ │ ├── popover.js
│ │ │ │ │ ├── scrollspy.js
│ │ │ │ │ ├── tab.js
│ │ │ │ │ ├── tooltip.js
│ │ │ │ │ └── transition.js
│ │ │ │ └── less/
│ │ │ │ ├── alerts.less
│ │ │ │ ├── badges.less
│ │ │ │ ├── bootstrap.less
│ │ │ │ ├── breadcrumbs.less
│ │ │ │ ├── button-groups.less
│ │ │ │ ├── buttons.less
│ │ │ │ ├── carousel.less
│ │ │ │ ├── close.less
│ │ │ │ ├── code.less
│ │ │ │ ├── component-animations.less
│ │ │ │ ├── dropdowns.less
│ │ │ │ ├── forms.less
│ │ │ │ ├── glyphicons.less
│ │ │ │ ├── grid.less
│ │ │ │ ├── input-groups.less
│ │ │ │ ├── jumbotron.less
│ │ │ │ ├── labels.less
│ │ │ │ ├── list-group.less
│ │ │ │ ├── media.less
│ │ │ │ ├── mixins.less
│ │ │ │ ├── modals.less
│ │ │ │ ├── navbar.less
│ │ │ │ ├── navs.less
│ │ │ │ ├── normalize.less
│ │ │ │ ├── pager.less
│ │ │ │ ├── pagination.less
│ │ │ │ ├── panels.less
│ │ │ │ ├── popovers.less
│ │ │ │ ├── print.less
│ │ │ │ ├── progress-bars.less
│ │ │ │ ├── responsive-utilities.less
│ │ │ │ ├── scaffolding.less
│ │ │ │ ├── tables.less
│ │ │ │ ├── theme.less
│ │ │ │ ├── thumbnails.less
│ │ │ │ ├── tooltip.less
│ │ │ │ ├── type.less
│ │ │ │ ├── utilities.less
│ │ │ │ ├── variables.less
│ │ │ │ └── wells.less
│ │ │ ├── jquery/
│ │ │ │ ├── .bower.json
│ │ │ │ ├── MIT-LICENSE.txt
│ │ │ │ ├── bower.json
│ │ │ │ ├── dist/
│ │ │ │ │ └── jquery.js
│ │ │ │ └── src/
│ │ │ │ ├── ajax/
│ │ │ │ │ ├── jsonp.js
│ │ │ │ │ ├── load.js
│ │ │ │ │ ├── parseJSON.js
│ │ │ │ │ ├── parseXML.js
│ │ │ │ │ ├── script.js
│ │ │ │ │ ├── var/
│ │ │ │ │ │ ├── nonce.js
│ │ │ │ │ │ └── rquery.js
│ │ │ │ │ └── xhr.js
│ │ │ │ ├── ajax.js
│ │ │ │ ├── attributes/
│ │ │ │ │ ├── attr.js
│ │ │ │ │ ├── classes.js
│ │ │ │ │ ├── prop.js
│ │ │ │ │ ├── support.js
│ │ │ │ │ └── val.js
│ │ │ │ ├── attributes.js
│ │ │ │ ├── callbacks.js
│ │ │ │ ├── core/
│ │ │ │ │ ├── access.js
│ │ │ │ │ ├── init.js
│ │ │ │ │ ├── parseHTML.js
│ │ │ │ │ ├── ready.js
│ │ │ │ │ └── var/
│ │ │ │ │ └── rsingleTag.js
│ │ │ │ ├── core.js
│ │ │ │ ├── css/
│ │ │ │ │ ├── addGetHookIf.js
│ │ │ │ │ ├── curCSS.js
│ │ │ │ │ ├── defaultDisplay.js
│ │ │ │ │ ├── hiddenVisibleSelectors.js
│ │ │ │ │ ├── support.js
│ │ │ │ │ ├── swap.js
│ │ │ │ │ └── var/
│ │ │ │ │ ├── cssExpand.js
│ │ │ │ │ ├── isHidden.js
│ │ │ │ │ ├── rmargin.js
│ │ │ │ │ └── rnumnonpx.js
│ │ │ │ ├── css.js
│ │ │ │ ├── data.js
│ │ │ │ ├── deferred.js
│ │ │ │ ├── deprecated.js
│ │ │ │ ├── dimensions.js
│ │ │ │ ├── effects/
│ │ │ │ │ ├── Tween.js
│ │ │ │ │ ├── animatedSelector.js
│ │ │ │ │ └── support.js
│ │ │ │ ├── effects.js
│ │ │ │ ├── event/
│ │ │ │ │ ├── alias.js
│ │ │ │ │ └── support.js
│ │ │ │ ├── event.js
│ │ │ │ ├── exports/
│ │ │ │ │ ├── amd.js
│ │ │ │ │ └── global.js
│ │ │ │ ├── intro.js
│ │ │ │ ├── jquery.js
│ │ │ │ ├── manipulation/
│ │ │ │ │ ├── _evalUrl.js
│ │ │ │ │ ├── support.js
│ │ │ │ │ └── var/
│ │ │ │ │ └── rcheckableType.js
│ │ │ │ ├── manipulation.js
│ │ │ │ ├── offset.js
│ │ │ │ ├── outro.js
│ │ │ │ ├── queue/
│ │ │ │ │ └── delay.js
│ │ │ │ ├── queue.js
│ │ │ │ ├── selector-sizzle.js
│ │ │ │ ├── selector.js
│ │ │ │ ├── serialize.js
│ │ │ │ ├── sizzle/
│ │ │ │ │ └── dist/
│ │ │ │ │ └── sizzle.js
│ │ │ │ ├── support.js
│ │ │ │ ├── traversing/
│ │ │ │ │ ├── findFilter.js
│ │ │ │ │ └── var/
│ │ │ │ │ └── rneedsContext.js
│ │ │ │ ├── traversing.js
│ │ │ │ ├── var/
│ │ │ │ │ ├── class2type.js
│ │ │ │ │ ├── concat.js
│ │ │ │ │ ├── deletedIds.js
│ │ │ │ │ ├── hasOwn.js
│ │ │ │ │ ├── indexOf.js
│ │ │ │ │ ├── pnum.js
│ │ │ │ │ ├── push.js
│ │ │ │ │ ├── rnotwhite.js
│ │ │ │ │ ├── slice.js
│ │ │ │ │ ├── strundefined.js
│ │ │ │ │ ├── support.js
│ │ │ │ │ └── toString.js
│ │ │ │ └── wrap.js
│ │ │ ├── ment.io/
│ │ │ │ ├── .bower.json
│ │ │ │ ├── LICENSE-MIT
│ │ │ │ ├── README.md
│ │ │ │ ├── bower.json
│ │ │ │ ├── dist/
│ │ │ │ │ ├── mentio.js
│ │ │ │ │ └── templates.js
│ │ │ │ ├── gulpfile.js
│ │ │ │ ├── karma.conf.js
│ │ │ │ ├── ment.io/
│ │ │ │ │ ├── index.html
│ │ │ │ │ ├── peopledata.json
│ │ │ │ │ ├── productdata.json
│ │ │ │ │ ├── scripts.js
│ │ │ │ │ ├── simplepeopledata.json
│ │ │ │ │ └── styles.css
│ │ │ │ ├── package.json
│ │ │ │ └── src/
│ │ │ │ ├── mentio-menu.tpl.html
│ │ │ │ ├── mentio.directive.js
│ │ │ │ └── mentio.service.js
│ │ │ ├── moment/
│ │ │ │ ├── .bower.json
│ │ │ │ ├── CHANGELOG.md
│ │ │ │ ├── LICENSE
│ │ │ │ ├── Moment.js.nuspec
│ │ │ │ ├── README.md
│ │ │ │ ├── benchmarks/
│ │ │ │ │ └── clone.js
│ │ │ │ ├── bower.json
│ │ │ │ ├── locale/
│ │ │ │ │ ├── af.js
│ │ │ │ │ ├── ar-ma.js
│ │ │ │ │ ├── ar-sa.js
│ │ │ │ │ ├── ar-tn.js
│ │ │ │ │ ├── ar.js
│ │ │ │ │ ├── az.js
│ │ │ │ │ ├── be.js
│ │ │ │ │ ├── bg.js
│ │ │ │ │ ├── bn.js
│ │ │ │ │ ├── bo.js
│ │ │ │ │ ├── br.js
│ │ │ │ │ ├── bs.js
│ │ │ │ │ ├── ca.js
│ │ │ │ │ ├── cs.js
│ │ │ │ │ ├── cv.js
│ │ │ │ │ ├── cy.js
│ │ │ │ │ ├── da.js
│ │ │ │ │ ├── de-at.js
│ │ │ │ │ ├── de.js
│ │ │ │ │ ├── el.js
│ │ │ │ │ ├── en-au.js
│ │ │ │ │ ├── en-ca.js
│ │ │ │ │ ├── en-gb.js
│ │ │ │ │ ├── eo.js
│ │ │ │ │ ├── es.js
│ │ │ │ │ ├── et.js
│ │ │ │ │ ├── eu.js
│ │ │ │ │ ├── fa.js
│ │ │ │ │ ├── fi.js
│ │ │ │ │ ├── fo.js
│ │ │ │ │ ├── fr-ca.js
│ │ │ │ │ ├── fr.js
│ │ │ │ │ ├── fy.js
│ │ │ │ │ ├── gl.js
│ │ │ │ │ ├── he.js
│ │ │ │ │ ├── hi.js
│ │ │ │ │ ├── hr.js
│ │ │ │ │ ├── hu.js
│ │ │ │ │ ├── hy-am.js
│ │ │ │ │ ├── id.js
│ │ │ │ │ ├── is.js
│ │ │ │ │ ├── it.js
│ │ │ │ │ ├── ja.js
│ │ │ │ │ ├── ka.js
│ │ │ │ │ ├── km.js
│ │ │ │ │ ├── ko.js
│ │ │ │ │ ├── lb.js
│ │ │ │ │ ├── lt.js
│ │ │ │ │ ├── lv.js
│ │ │ │ │ ├── mk.js
│ │ │ │ │ ├── ml.js
│ │ │ │ │ ├── mr.js
│ │ │ │ │ ├── ms-my.js
│ │ │ │ │ ├── my.js
│ │ │ │ │ ├── nb.js
│ │ │ │ │ ├── ne.js
│ │ │ │ │ ├── nl.js
│ │ │ │ │ ├── nn.js
│ │ │ │ │ ├── pl.js
│ │ │ │ │ ├── pt-br.js
│ │ │ │ │ ├── pt.js
│ │ │ │ │ ├── ro.js
│ │ │ │ │ ├── ru.js
│ │ │ │ │ ├── sk.js
│ │ │ │ │ ├── sl.js
│ │ │ │ │ ├── sq.js
│ │ │ │ │ ├── sr-cyrl.js
│ │ │ │ │ ├── sr.js
│ │ │ │ │ ├── sv.js
│ │ │ │ │ ├── ta.js
│ │ │ │ │ ├── th.js
│ │ │ │ │ ├── tl-ph.js
│ │ │ │ │ ├── tr.js
│ │ │ │ │ ├── tzm-latn.js
│ │ │ │ │ ├── tzm.js
│ │ │ │ │ ├── uk.js
│ │ │ │ │ ├── uz.js
│ │ │ │ │ ├── vi.js
│ │ │ │ │ ├── zh-cn.js
│ │ │ │ │ └── zh-tw.js
│ │ │ │ ├── meteor/
│ │ │ │ │ ├── README.md
│ │ │ │ │ ├── export.js
│ │ │ │ │ └── test.js
│ │ │ │ ├── min/
│ │ │ │ │ ├── locales.js
│ │ │ │ │ ├── moment-with-locales.js
│ │ │ │ │ └── tests.js
│ │ │ │ ├── moment.js
│ │ │ │ ├── scripts/
│ │ │ │ │ └── npm_prepublish.sh
│ │ │ │ ├── src/
│ │ │ │ │ ├── lib/
│ │ │ │ │ │ ├── create/
│ │ │ │ │ │ │ ├── check-overflow.js
│ │ │ │ │ │ │ ├── date-from-array.js
│ │ │ │ │ │ │ ├── default-parsing-flags.js
│ │ │ │ │ │ │ ├── from-anything.js
│ │ │ │ │ │ │ ├── from-array.js
│ │ │ │ │ │ │ ├── from-object.js
│ │ │ │ │ │ │ ├── from-string-and-array.js
│ │ │ │ │ │ │ ├── from-string-and-format.js
│ │ │ │ │ │ │ ├── from-string.js
│ │ │ │ │ │ │ ├── local.js
│ │ │ │ │ │ │ ├── utc.js
│ │ │ │ │ │ │ └── valid.js
│ │ │ │ │ │ ├── duration/
│ │ │ │ │ │ │ ├── abs.js
│ │ │ │ │ │ │ ├── add-subtract.js
│ │ │ │ │ │ │ ├── as.js
│ │ │ │ │ │ │ ├── bubble.js
│ │ │ │ │ │ │ ├── constructor.js
│ │ │ │ │ │ │ ├── create.js
│ │ │ │ │ │ │ ├── duration.js
│ │ │ │ │ │ │ ├── get.js
│ │ │ │ │ │ │ ├── humanize.js
│ │ │ │ │ │ │ ├── iso-string.js
│ │ │ │ │ │ │ └── prototype.js
│ │ │ │ │ │ ├── format/
│ │ │ │ │ │ │ └── format.js
│ │ │ │ │ │ ├── locale/
│ │ │ │ │ │ │ ├── calendar.js
│ │ │ │ │ │ │ ├── constructor.js
│ │ │ │ │ │ │ ├── en.js
│ │ │ │ │ │ │ ├── formats.js
│ │ │ │ │ │ │ ├── invalid.js
│ │ │ │ │ │ │ ├── lists.js
│ │ │ │ │ │ │ ├── locale.js
│ │ │ │ │ │ │ ├── locales.js
│ │ │ │ │ │ │ ├── ordinal.js
│ │ │ │ │ │ │ ├── pre-post-format.js
│ │ │ │ │ │ │ ├── prototype.js
│ │ │ │ │ │ │ ├── relative.js
│ │ │ │ │ │ │ └── set.js
│ │ │ │ │ │ ├── moment/
│ │ │ │ │ │ │ ├── add-subtract.js
│ │ │ │ │ │ │ ├── calendar.js
│ │ │ │ │ │ │ ├── clone.js
│ │ │ │ │ │ │ ├── compare.js
│ │ │ │ │ │ │ ├── constructor.js
│ │ │ │ │ │ │ ├── diff.js
│ │ │ │ │ │ │ ├── format.js
│ │ │ │ │ │ │ ├── from.js
│ │ │ │ │ │ │ ├── get-set.js
│ │ │ │ │ │ │ ├── locale.js
│ │ │ │ │ │ │ ├── min-max.js
│ │ │ │ │ │ │ ├── moment.js
│ │ │ │ │ │ │ ├── prototype.js
│ │ │ │ │ │ │ ├── start-end-of.js
│ │ │ │ │ │ │ ├── to-type.js
│ │ │ │ │ │ │ └── valid.js
│ │ │ │ │ │ ├── parse/
│ │ │ │ │ │ │ ├── regex.js
│ │ │ │ │ │ │ └── token.js
│ │ │ │ │ │ ├── units/
│ │ │ │ │ │ │ ├── aliases.js
│ │ │ │ │ │ │ ├── constants.js
│ │ │ │ │ │ │ ├── day-of-month.js
│ │ │ │ │ │ │ ├── day-of-week.js
│ │ │ │ │ │ │ ├── day-of-year.js
│ │ │ │ │ │ │ ├── hour.js
│ │ │ │ │ │ │ ├── millisecond.js
│ │ │ │ │ │ │ ├── minute.js
│ │ │ │ │ │ │ ├── month.js
│ │ │ │ │ │ │ ├── offset.js
│ │ │ │ │ │ │ ├── quarter.js
│ │ │ │ │ │ │ ├── second.js
│ │ │ │ │ │ │ ├── timestamp.js
│ │ │ │ │ │ │ ├── timezone.js
│ │ │ │ │ │ │ ├── units.js
│ │ │ │ │ │ │ ├── week-year.js
│ │ │ │ │ │ │ ├── week.js
│ │ │ │ │ │ │ └── year.js
│ │ │ │ │ │ └── utils/
│ │ │ │ │ │ ├── abs-floor.js
│ │ │ │ │ │ ├── compare-arrays.js
│ │ │ │ │ │ ├── defaults.js
│ │ │ │ │ │ ├── deprecate.js
│ │ │ │ │ │ ├── extend.js
│ │ │ │ │ │ ├── has-own-prop.js
│ │ │ │ │ │ ├── hooks.js
│ │ │ │ │ │ ├── is-array.js
│ │ │ │ │ │ ├── is-date.js
│ │ │ │ │ │ ├── map.js
│ │ │ │ │ │ ├── to-int.js
│ │ │ │ │ │ └── zero-fill.js
│ │ │ │ │ ├── locale/
│ │ │ │ │ │ ├── af.js
│ │ │ │ │ │ ├── ar-ma.js
│ │ │ │ │ │ ├── ar-sa.js
│ │ │ │ │ │ ├── ar-tn.js
│ │ │ │ │ │ ├── ar.js
│ │ │ │ │ │ ├── az.js
│ │ │ │ │ │ ├── be.js
│ │ │ │ │ │ ├── bg.js
│ │ │ │ │ │ ├── bn.js
│ │ │ │ │ │ ├── bo.js
│ │ │ │ │ │ ├── br.js
│ │ │ │ │ │ ├── bs.js
│ │ │ │ │ │ ├── ca.js
│ │ │ │ │ │ ├── cs.js
│ │ │ │ │ │ ├── cv.js
│ │ │ │ │ │ ├── cy.js
│ │ │ │ │ │ ├── da.js
│ │ │ │ │ │ ├── de-at.js
│ │ │ │ │ │ ├── de.js
│ │ │ │ │ │ ├── el.js
│ │ │ │ │ │ ├── en-au.js
│ │ │ │ │ │ ├── en-ca.js
│ │ │ │ │ │ ├── en-gb.js
│ │ │ │ │ │ ├── eo.js
│ │ │ │ │ │ ├── es.js
│ │ │ │ │ │ ├── et.js
│ │ │ │ │ │ ├── eu.js
│ │ │ │ │ │ ├── fa.js
│ │ │ │ │ │ ├── fi.js
│ │ │ │ │ │ ├── fo.js
│ │ │ │ │ │ ├── fr-ca.js
│ │ │ │ │ │ ├── fr.js
│ │ │ │ │ │ ├── fy.js
│ │ │ │ │ │ ├── gl.js
│ │ │ │ │ │ ├── he.js
│ │ │ │ │ │ ├── hi.js
│ │ │ │ │ │ ├── hr.js
│ │ │ │ │ │ ├── hu.js
│ │ │ │ │ │ ├── hy-am.js
│ │ │ │ │ │ ├── id.js
│ │ │ │ │ │ ├── is.js
│ │ │ │ │ │ ├── it.js
│ │ │ │ │ │ ├── ja.js
│ │ │ │ │ │ ├── ka.js
│ │ │ │ │ │ ├── km.js
│ │ │ │ │ │ ├── ko.js
│ │ │ │ │ │ ├── lb.js
│ │ │ │ │ │ ├── lt.js
│ │ │ │ │ │ ├── lv.js
│ │ │ │ │ │ ├── mk.js
│ │ │ │ │ │ ├── ml.js
│ │ │ │ │ │ ├── mr.js
│ │ │ │ │ │ ├── ms-my.js
│ │ │ │ │ │ ├── my.js
│ │ │ │ │ │ ├── nb.js
│ │ │ │ │ │ ├── ne.js
│ │ │ │ │ │ ├── nl.js
│ │ │ │ │ │ ├── nn.js
│ │ │ │ │ │ ├── pl.js
│ │ │ │ │ │ ├── pt-br.js
│ │ │ │ │ │ ├── pt.js
│ │ │ │ │ │ ├── ro.js
│ │ │ │ │ │ ├── ru.js
│ │ │ │ │ │ ├── sk.js
│ │ │ │ │ │ ├── sl.js
│ │ │ │ │ │ ├── sq.js
│ │ │ │ │ │ ├── sr-cyrl.js
│ │ │ │ │ │ ├── sr.js
│ │ │ │ │ │ ├── sv.js
│ │ │ │ │ │ ├── ta.js
│ │ │ │ │ │ ├── th.js
│ │ │ │ │ │ ├── tl-ph.js
│ │ │ │ │ │ ├── tr.js
│ │ │ │ │ │ ├── tzm-latn.js
│ │ │ │ │ │ ├── tzm.js
│ │ │ │ │ │ ├── uk.js
│ │ │ │ │ │ ├── uz.js
│ │ │ │ │ │ ├── vi.js
│ │ │ │ │ │ ├── zh-cn.js
│ │ │ │ │ │ └── zh-tw.js
│ │ │ │ │ └── moment.js
│ │ │ │ └── templates/
│ │ │ │ ├── amd-named.js
│ │ │ │ ├── amd.js
│ │ │ │ ├── globals.js
│ │ │ │ ├── locale-header.js
│ │ │ │ └── test-header.js
│ │ │ ├── ng-file-upload/
│ │ │ │ ├── .bower.json
│ │ │ │ ├── FileAPI.flash.swf
│ │ │ │ ├── FileAPI.js
│ │ │ │ ├── LICENSE
│ │ │ │ ├── README.md
│ │ │ │ ├── angular-file-upload-all.js
│ │ │ │ ├── angular-file-upload-shim.js
│ │ │ │ ├── angular-file-upload.js
│ │ │ │ ├── bower.json
│ │ │ │ └── ng-file-upload-all.js
│ │ │ ├── ngInfiniteScroll/
│ │ │ │ ├── .bower.json
│ │ │ │ ├── LICENSE
│ │ │ │ ├── README.md
│ │ │ │ ├── bower.json
│ │ │ │ ├── build/
│ │ │ │ │ └── ng-infinite-scroll.js
│ │ │ │ ├── package.json
│ │ │ │ └── src/
│ │ │ │ └── infinite-scroll.coffee
│ │ │ ├── ngtoast/
│ │ │ │ ├── .bower.json
│ │ │ │ ├── README.md
│ │ │ │ ├── bower.json
│ │ │ │ └── dist/
│ │ │ │ ├── ngToast-animations.css
│ │ │ │ ├── ngToast.css
│ │ │ │ └── ngToast.js
│ │ │ └── openlayers/
│ │ │ ├── .bower.json
│ │ │ ├── .gitignore
│ │ │ ├── apidoc_config/
│ │ │ │ ├── Languages.txt
│ │ │ │ ├── Menu.txt
│ │ │ │ ├── OL.css
│ │ │ │ └── Topics.txt
│ │ │ ├── authors.txt
│ │ │ ├── build/
│ │ │ │ ├── README.txt
│ │ │ │ ├── build.py
│ │ │ │ ├── buildUncompressed.py
│ │ │ │ ├── closure-compiler/
│ │ │ │ │ └── Externs.js
│ │ │ │ ├── full.cfg
│ │ │ │ ├── license.txt
│ │ │ │ ├── light.cfg
│ │ │ │ ├── lite.cfg
│ │ │ │ ├── mobile.cfg
│ │ │ │ └── tests.cfg
│ │ │ ├── doc_config/
│ │ │ │ ├── Languages.txt
│ │ │ │ ├── Menu.txt
│ │ │ │ ├── OL.css
│ │ │ │ └── Topics.txt
│ │ │ ├── lib/
│ │ │ │ ├── Firebug/
│ │ │ │ │ ├── firebug.css
│ │ │ │ │ ├── firebug.html
│ │ │ │ │ ├── firebug.js
│ │ │ │ │ ├── firebugx.js
│ │ │ │ │ ├── license.txt
│ │ │ │ │ └── readme.txt
│ │ │ │ ├── OpenLayers/
│ │ │ │ │ ├── Animation.js
│ │ │ │ │ ├── BaseTypes/
│ │ │ │ │ │ ├── Bounds.js
│ │ │ │ │ │ ├── Class.js
│ │ │ │ │ │ ├── Date.js
│ │ │ │ │ │ ├── Element.js
│ │ │ │ │ │ ├── LonLat.js
│ │ │ │ │ │ ├── Pixel.js
│ │ │ │ │ │ └── Size.js
│ │ │ │ │ ├── BaseTypes.js
│ │ │ │ │ ├── Console.js
│ │ │ │ │ ├── Control/
│ │ │ │ │ │ ├── ArgParser.js
│ │ │ │ │ │ ├── Attribution.js
│ │ │ │ │ │ ├── Button.js
│ │ │ │ │ │ ├── CacheRead.js
│ │ │ │ │ │ ├── CacheWrite.js
│ │ │ │ │ │ ├── DragFeature.js
│ │ │ │ │ │ ├── DragPan.js
│ │ │ │ │ │ ├── DrawFeature.js
│ │ │ │ │ │ ├── EditingToolbar.js
│ │ │ │ │ │ ├── Geolocate.js
│ │ │ │ │ │ ├── GetFeature.js
│ │ │ │ │ │ ├── Graticule.js
│ │ │ │ │ │ ├── KeyboardDefaults.js
│ │ │ │ │ │ ├── LayerSwitcher.js
│ │ │ │ │ │ ├── Measure.js
│ │ │ │ │ │ ├── ModifyFeature/
│ │ │ │ │ │ │ └── BySegment.js
│ │ │ │ │ │ ├── ModifyFeature.js
│ │ │ │ │ │ ├── MousePosition.js
│ │ │ │ │ │ ├── NavToolbar.js
│ │ │ │ │ │ ├── Navigation.js
│ │ │ │ │ │ ├── NavigationHistory.js
│ │ │ │ │ │ ├── OverviewMap.js
│ │ │ │ │ │ ├── Pan.js
│ │ │ │ │ │ ├── PanPanel.js
│ │ │ │ │ │ ├── PanZoom.js
│ │ │ │ │ │ ├── PanZoomBar.js
│ │ │ │ │ │ ├── Panel.js
│ │ │ │ │ │ ├── Permalink.js
│ │ │ │ │ │ ├── PinchZoom.js
│ │ │ │ │ │ ├── SLDSelect.js
│ │ │ │ │ │ ├── Scale.js
│ │ │ │ │ │ ├── ScaleLine.js
│ │ │ │ │ │ ├── SelectFeature.js
│ │ │ │ │ │ ├── Snapping.js
│ │ │ │ │ │ ├── Split.js
│ │ │ │ │ │ ├── TextButtonPanel.js
│ │ │ │ │ │ ├── TouchNavigation.js
│ │ │ │ │ │ ├── TransformFeature.js
│ │ │ │ │ │ ├── UTFGrid.js
│ │ │ │ │ │ ├── WMSGetFeatureInfo.js
│ │ │ │ │ │ ├── WMTSGetFeatureInfo.js
│ │ │ │ │ │ ├── Zoom.js
│ │ │ │ │ │ ├── ZoomBox.js
│ │ │ │ │ │ ├── ZoomIn.js
│ │ │ │ │ │ ├── ZoomOut.js
│ │ │ │ │ │ ├── ZoomPanel.js
│ │ │ │ │ │ └── ZoomToMaxExtent.js
│ │ │ │ │ ├── Control.js
│ │ │ │ │ ├── Events/
│ │ │ │ │ │ ├── buttonclick.js
│ │ │ │ │ │ └── featureclick.js
│ │ │ │ │ ├── Events.js
│ │ │ │ │ ├── Feature/
│ │ │ │ │ │ └── Vector.js
│ │ │ │ │ ├── Feature.js
│ │ │ │ │ ├── Filter/
│ │ │ │ │ │ ├── Comparison.js
│ │ │ │ │ │ ├── FeatureId.js
│ │ │ │ │ │ ├── Function.js
│ │ │ │ │ │ ├── Logical.js
│ │ │ │ │ │ └── Spatial.js
│ │ │ │ │ ├── Filter.js
│ │ │ │ │ ├── Format/
│ │ │ │ │ │ ├── ArcXML/
│ │ │ │ │ │ │ └── Features.js
│ │ │ │ │ │ ├── ArcXML.js
│ │ │ │ │ │ ├── Atom.js
│ │ │ │ │ │ ├── CQL.js
│ │ │ │ │ │ ├── CSWGetDomain/
│ │ │ │ │ │ │ └── v2_0_2.js
│ │ │ │ │ │ ├── CSWGetDomain.js
│ │ │ │ │ │ ├── CSWGetRecords/
│ │ │ │ │ │ │ └── v2_0_2.js
│ │ │ │ │ │ ├── CSWGetRecords.js
│ │ │ │ │ │ ├── Context.js
│ │ │ │ │ │ ├── EncodedPolyline.js
│ │ │ │ │ │ ├── Filter/
│ │ │ │ │ │ │ ├── v1.js
│ │ │ │ │ │ │ ├── v1_0_0.js
│ │ │ │ │ │ │ ├── v1_1_0.js
│ │ │ │ │ │ │ ├── v2.js
│ │ │ │ │ │ │ └── v2_0_0.js
│ │ │ │ │ │ ├── Filter.js
│ │ │ │ │ │ ├── GML/
│ │ │ │ │ │ │ ├── Base.js
│ │ │ │ │ │ │ ├── v2.js
│ │ │ │ │ │ │ └── v3.js
│ │ │ │ │ │ ├── GML.js
│ │ │ │ │ │ ├── GPX.js
│ │ │ │ │ │ ├── GeoJSON.js
│ │ │ │ │ │ ├── GeoRSS.js
│ │ │ │ │ │ ├── JSON.js
│ │ │ │ │ │ ├── KML.js
│ │ │ │ │ │ ├── OGCExceptionReport.js
│ │ │ │ │ │ ├── OSM.js
│ │ │ │ │ │ ├── OWSCommon/
│ │ │ │ │ │ │ ├── v1.js
│ │ │ │ │ │ │ ├── v1_0_0.js
│ │ │ │ │ │ │ └── v1_1_0.js
│ │ │ │ │ │ ├── OWSCommon.js
│ │ │ │ │ │ ├── OWSContext/
│ │ │ │ │ │ │ └── v0_3_1.js
│ │ │ │ │ │ ├── OWSContext.js
│ │ │ │ │ │ ├── QueryStringFilter.js
│ │ │ │ │ │ ├── SLD/
│ │ │ │ │ │ │ ├── v1.js
│ │ │ │ │ │ │ ├── v1_0_0.js
│ │ │ │ │ │ │ └── v1_0_0_GeoServer.js
│ │ │ │ │ │ ├── SLD.js
│ │ │ │ │ │ ├── SOSCapabilities/
│ │ │ │ │ │ │ └── v1_0_0.js
│ │ │ │ │ │ ├── SOSCapabilities.js
│ │ │ │ │ │ ├── SOSGetFeatureOfInterest.js
│ │ │ │ │ │ ├── SOSGetObservation.js
│ │ │ │ │ │ ├── TMSCapabilities.js
│ │ │ │ │ │ ├── Text.js
│ │ │ │ │ │ ├── WCSCapabilities/
│ │ │ │ │ │ │ ├── v1.js
│ │ │ │ │ │ │ ├── v1_0_0.js
│ │ │ │ │ │ │ └── v1_1_0.js
│ │ │ │ │ │ ├── WCSCapabilities.js
│ │ │ │ │ │ ├── WCSDescribeCoverage/
│ │ │ │ │ │ │ ├── v1.js
│ │ │ │ │ │ │ ├── v1_0_0.js
│ │ │ │ │ │ │ └── v1_1_0.js
│ │ │ │ │ │ ├── WCSDescribeCoverage.js
│ │ │ │ │ │ ├── WCSGetCoverage.js
│ │ │ │ │ │ ├── WFS.js
│ │ │ │ │ │ ├── WFSCapabilities/
│ │ │ │ │ │ │ ├── v1.js
│ │ │ │ │ │ │ ├── v1_0_0.js
│ │ │ │ │ │ │ ├── v1_1_0.js
│ │ │ │ │ │ │ └── v2_0_0.js
│ │ │ │ │ │ ├── WFSCapabilities.js
│ │ │ │ │ │ ├── WFSDescribeFeatureType.js
│ │ │ │ │ │ ├── WFST/
│ │ │ │ │ │ │ ├── v1.js
│ │ │ │ │ │ │ ├── v1_0_0.js
│ │ │ │ │ │ │ ├── v1_1_0.js
│ │ │ │ │ │ │ └── v2_0_0.js
│ │ │ │ │ │ ├── WFST.js
│ │ │ │ │ │ ├── WKT.js
│ │ │ │ │ │ ├── WMC/
│ │ │ │ │ │ │ ├── v1.js
│ │ │ │ │ │ │ ├── v1_0_0.js
│ │ │ │ │ │ │ └── v1_1_0.js
│ │ │ │ │ │ ├── WMC.js
│ │ │ │ │ │ ├── WMSCapabilities/
│ │ │ │ │ │ │ ├── v1.js
│ │ │ │ │ │ │ ├── v1_1.js
│ │ │ │ │ │ │ ├── v1_1_0.js
│ │ │ │ │ │ │ ├── v1_1_1.js
│ │ │ │ │ │ │ ├── v1_1_1_WMSC.js
│ │ │ │ │ │ │ ├── v1_3.js
│ │ │ │ │ │ │ └── v1_3_0.js
│ │ │ │ │ │ ├── WMSCapabilities.js
│ │ │ │ │ │ ├── WMSDescribeLayer/
│ │ │ │ │ │ │ └── v1_1.js
│ │ │ │ │ │ ├── WMSDescribeLayer.js
│ │ │ │ │ │ ├── WMSGetFeatureInfo.js
│ │ │ │ │ │ ├── WMTSCapabilities/
│ │ │ │ │ │ │ └── v1_0_0.js
│ │ │ │ │ │ ├── WMTSCapabilities.js
│ │ │ │ │ │ ├── WPSCapabilities/
│ │ │ │ │ │ │ └── v1_0_0.js
│ │ │ │ │ │ ├── WPSCapabilities.js
│ │ │ │ │ │ ├── WPSDescribeProcess/
│ │ │ │ │ │ │ └── v1_0_0.js
│ │ │ │ │ │ ├── WPSDescribeProcess.js
│ │ │ │ │ │ ├── WPSExecute.js
│ │ │ │ │ │ ├── XLS/
│ │ │ │ │ │ │ ├── v1.js
│ │ │ │ │ │ │ └── v1_1_0.js
│ │ │ │ │ │ ├── XLS.js
│ │ │ │ │ │ ├── XML/
│ │ │ │ │ │ │ └── VersionedOGC.js
│ │ │ │ │ │ └── XML.js
│ │ │ │ │ ├── Format.js
│ │ │ │ │ ├── Geometry/
│ │ │ │ │ │ ├── Collection.js
│ │ │ │ │ │ ├── Curve.js
│ │ │ │ │ │ ├── LineString.js
│ │ │ │ │ │ ├── LinearRing.js
│ │ │ │ │ │ ├── MultiLineString.js
│ │ │ │ │ │ ├── MultiPoint.js
│ │ │ │ │ │ ├── MultiPolygon.js
│ │ │ │ │ │ ├── Point.js
│ │ │ │ │ │ └── Polygon.js
│ │ │ │ │ ├── Geometry.js
│ │ │ │ │ ├── Handler/
│ │ │ │ │ │ ├── Box.js
│ │ │ │ │ │ ├── Click.js
│ │ │ │ │ │ ├── Drag.js
│ │ │ │ │ │ ├── Feature.js
│ │ │ │ │ │ ├── Hover.js
│ │ │ │ │ │ ├── Keyboard.js
│ │ │ │ │ │ ├── MouseWheel.js
│ │ │ │ │ │ ├── Path.js
│ │ │ │ │ │ ├── Pinch.js
│ │ │ │ │ │ ├── Point.js
│ │ │ │ │ │ ├── Polygon.js
│ │ │ │ │ │ └── RegularPolygon.js
│ │ │ │ │ ├── Handler.js
│ │ │ │ │ ├── Icon.js
│ │ │ │ │ ├── Kinetic.js
│ │ │ │ │ ├── Lang/
│ │ │ │ │ │ ├── ar.js
│ │ │ │ │ │ ├── be-tarask.js
│ │ │ │ │ │ ├── bg.js
│ │ │ │ │ │ ├── br.js
│ │ │ │ │ │ ├── ca.js
│ │ │ │ │ │ ├── cs-CZ.js
│ │ │ │ │ │ ├── da-DK.js
│ │ │ │ │ │ ├── de.js
│ │ │ │ │ │ ├── el.js
│ │ │ │ │ │ ├── en-CA.js
│ │ │ │ │ │ ├── en.js
│ │ │ │ │ │ ├── es.js
│ │ │ │ │ │ ├── fi.js
│ │ │ │ │ │ ├── fr.js
│ │ │ │ │ │ ├── fur.js
│ │ │ │ │ │ ├── gl.js
│ │ │ │ │ │ ├── gsw.js
│ │ │ │ │ │ ├── hr.js
│ │ │ │ │ │ ├── hsb.js
│ │ │ │ │ │ ├── hu.js
│ │ │ │ │ │ ├── ia.js
│ │ │ │ │ │ ├── id.js
│ │ │ │ │ │ ├── io.js
│ │ │ │ │ │ ├── is.js
│ │ │ │ │ │ ├── it.js
│ │ │ │ │ │ ├── ja.js
│ │ │ │ │ │ ├── km.js
│ │ │ │ │ │ ├── ksh.js
│ │ │ │ │ │ ├── lt.js
│ │ │ │ │ │ ├── nb.js
│ │ │ │ │ │ ├── nds.js
│ │ │ │ │ │ ├── nl.js
│ │ │ │ │ │ ├── nn.js
│ │ │ │ │ │ ├── oc.js
│ │ │ │ │ │ ├── pl.js
│ │ │ │ │ │ ├── pt-BR.js
│ │ │ │ │ │ ├── pt.js
│ │ │ │ │ │ ├── ro.js
│ │ │ │ │ │ ├── ru.js
│ │ │ │ │ │ ├── sk.js
│ │ │ │ │ │ ├── sv-SE.js
│ │ │ │ │ │ ├── te.js
│ │ │ │ │ │ ├── vi.js
│ │ │ │ │ │ ├── zh-CN.js
│ │ │ │ │ │ └── zh-TW.js
│ │ │ │ │ ├── Lang.js
│ │ │ │ │ ├── Layer/
│ │ │ │ │ │ ├── ArcGIS93Rest.js
│ │ │ │ │ │ ├── ArcGISCache.js
│ │ │ │ │ │ ├── ArcIMS.js
│ │ │ │ │ │ ├── Bing.js
│ │ │ │ │ │ ├── Boxes.js
│ │ │ │ │ │ ├── EventPane.js
│ │ │ │ │ │ ├── FixedZoomLevels.js
│ │ │ │ │ │ ├── GeoRSS.js
│ │ │ │ │ │ ├── Google/
│ │ │ │ │ │ │ └── v3.js
│ │ │ │ │ │ ├── Google.js
│ │ │ │ │ │ ├── Grid.js
│ │ │ │ │ │ ├── HTTPRequest.js
│ │ │ │ │ │ ├── Image.js
│ │ │ │ │ │ ├── KaMap.js
│ │ │ │ │ │ ├── KaMapCache.js
│ │ │ │ │ │ ├── MapGuide.js
│ │ │ │ │ │ ├── MapServer.js
│ │ │ │ │ │ ├── Markers.js
│ │ │ │ │ │ ├── OSM.js
│ │ │ │ │ │ ├── PointGrid.js
│ │ │ │ │ │ ├── PointTrack.js
│ │ │ │ │ │ ├── SphericalMercator.js
│ │ │ │ │ │ ├── TMS.js
│ │ │ │ │ │ ├── Text.js
│ │ │ │ │ │ ├── TileCache.js
│ │ │ │ │ │ ├── UTFGrid.js
│ │ │ │ │ │ ├── Vector/
│ │ │ │ │ │ │ └── RootContainer.js
│ │ │ │ │ │ ├── Vector.js
│ │ │ │ │ │ ├── WMS.js
│ │ │ │ │ │ ├── WMTS.js
│ │ │ │ │ │ ├── WorldWind.js
│ │ │ │ │ │ ├── XYZ.js
│ │ │ │ │ │ └── Zoomify.js
│ │ │ │ │ ├── Layer.js
│ │ │ │ │ ├── Map.js
│ │ │ │ │ ├── Marker/
│ │ │ │ │ │ └── Box.js
│ │ │ │ │ ├── Marker.js
│ │ │ │ │ ├── Popup/
│ │ │ │ │ │ ├── Anchored.js
│ │ │ │ │ │ ├── Framed.js
│ │ │ │ │ │ └── FramedCloud.js
│ │ │ │ │ ├── Popup.js
│ │ │ │ │ ├── Projection.js
│ │ │ │ │ ├── Protocol/
│ │ │ │ │ │ ├── CSW/
│ │ │ │ │ │ │ └── v2_0_2.js
│ │ │ │ │ │ ├── CSW.js
│ │ │ │ │ │ ├── HTTP.js
│ │ │ │ │ │ ├── SOS/
│ │ │ │ │ │ │ └── v1_0_0.js
│ │ │ │ │ │ ├── SOS.js
│ │ │ │ │ │ ├── Script.js
│ │ │ │ │ │ ├── WFS/
│ │ │ │ │ │ │ ├── v1.js
│ │ │ │ │ │ │ ├── v1_0_0.js
│ │ │ │ │ │ │ ├── v1_1_0.js
│ │ │ │ │ │ │ └── v2_0_0.js
│ │ │ │ │ │ └── WFS.js
│ │ │ │ │ ├── Protocol.js
│ │ │ │ │ ├── Renderer/
│ │ │ │ │ │ ├── Canvas.js
│ │ │ │ │ │ ├── Elements.js
│ │ │ │ │ │ ├── SVG.js
│ │ │ │ │ │ └── VML.js
│ │ │ │ │ ├── Renderer.js
│ │ │ │ │ ├── Request/
│ │ │ │ │ │ └── XMLHttpRequest.js
│ │ │ │ │ ├── Request.js
│ │ │ │ │ ├── Rule.js
│ │ │ │ │ ├── SingleFile.js
│ │ │ │ │ ├── Spherical.js
│ │ │ │ │ ├── Strategy/
│ │ │ │ │ │ ├── BBOX.js
│ │ │ │ │ │ ├── Cluster.js
│ │ │ │ │ │ ├── Filter.js
│ │ │ │ │ │ ├── Fixed.js
│ │ │ │ │ │ ├── Paging.js
│ │ │ │ │ │ ├── Refresh.js
│ │ │ │ │ │ └── Save.js
│ │ │ │ │ ├── Strategy.js
│ │ │ │ │ ├── Style.js
│ │ │ │ │ ├── Style2.js
│ │ │ │ │ ├── StyleMap.js
│ │ │ │ │ ├── Symbolizer/
│ │ │ │ │ │ ├── Line.js
│ │ │ │ │ │ ├── Point.js
│ │ │ │ │ │ ├── Polygon.js
│ │ │ │ │ │ ├── Raster.js
│ │ │ │ │ │ └── Text.js
│ │ │ │ │ ├── Symbolizer.js
│ │ │ │ │ ├── Tile/
│ │ │ │ │ │ ├── Image/
│ │ │ │ │ │ │ └── IFrame.js
│ │ │ │ │ │ ├── Image.js
│ │ │ │ │ │ └── UTFGrid.js
│ │ │ │ │ ├── Tile.js
│ │ │ │ │ ├── TileManager.js
│ │ │ │ │ ├── Tween.js
│ │ │ │ │ ├── Util/
│ │ │ │ │ │ └── vendorPrefix.js
│ │ │ │ │ ├── Util.js
│ │ │ │ │ ├── WPSClient.js
│ │ │ │ │ └── WPSProcess.js
│ │ │ │ ├── OpenLayers.js
│ │ │ │ ├── Rico/
│ │ │ │ │ ├── Color.js
│ │ │ │ │ ├── Corner.js
│ │ │ │ │ └── license.js
│ │ │ │ └── deprecated.js
│ │ │ ├── license.txt
│ │ │ ├── licenses/
│ │ │ │ ├── APACHE-2.0.txt
│ │ │ │ ├── BSD-LICENSE.txt
│ │ │ │ └── MIT-LICENSE.txt
│ │ │ ├── notes/
│ │ │ │ ├── 2.12.md
│ │ │ │ ├── 2.13.md
│ │ │ │ └── 2.14.md
│ │ │ ├── readme.md
│ │ │ ├── tests/
│ │ │ │ ├── Animation.html
│ │ │ │ ├── BaseTypes/
│ │ │ │ │ ├── Bounds.html
│ │ │ │ │ ├── Class.html
│ │ │ │ │ ├── Date.html
│ │ │ │ │ ├── Element.html
│ │ │ │ │ ├── LonLat.html
│ │ │ │ │ ├── Pixel.html
│ │ │ │ │ └── Size.html
│ │ │ │ ├── BaseTypes.html
│ │ │ │ ├── Console.html
│ │ │ │ ├── Control/
│ │ │ │ │ ├── ArgParser.html
│ │ │ │ │ ├── Attribution.html
│ │ │ │ │ ├── Button.html
│ │ │ │ │ ├── CacheRead.html
│ │ │ │ │ ├── CacheWrite.html
│ │ │ │ │ ├── DragFeature.html
│ │ │ │ │ ├── DragPan.html
│ │ │ │ │ ├── DrawFeature.html
│ │ │ │ │ ├── EditingToolbar.html
│ │ │ │ │ ├── Geolocate.html
│ │ │ │ │ ├── GetFeature.html
│ │ │ │ │ ├── Graticule.html
│ │ │ │ │ ├── KeyboardDefaults.html
│ │ │ │ │ ├── LayerSwitcher.html
│ │ │ │ │ ├── Measure.html
│ │ │ │ │ ├── ModifyFeature/
│ │ │ │ │ │ └── BySegment.html
│ │ │ │ │ ├── ModifyFeature.html
│ │ │ │ │ ├── MousePosition.html
│ │ │ │ │ ├── NavToolbar.html
│ │ │ │ │ ├── Navigation.html
│ │ │ │ │ ├── NavigationHistory.html
│ │ │ │ │ ├── OverviewMap.html
│ │ │ │ │ ├── Pan.html
│ │ │ │ │ ├── PanPanel.html
│ │ │ │ │ ├── PanZoom.html
│ │ │ │ │ ├── PanZoomBar.html
│ │ │ │ │ ├── Panel.html
│ │ │ │ │ ├── Permalink.html
│ │ │ │ │ ├── PinchZoom.html
│ │ │ │ │ ├── SLDSelect.html
│ │ │ │ │ ├── Scale.html
│ │ │ │ │ ├── ScaleLine.html
│ │ │ │ │ ├── SelectFeature.html
│ │ │ │ │ ├── Snapping.html
│ │ │ │ │ ├── Split.html
│ │ │ │ │ ├── TextButtonPanel.html
│ │ │ │ │ ├── TouchNavigation.html
│ │ │ │ │ ├── TransformFeature.html
│ │ │ │ │ ├── UTFGrid.html
│ │ │ │ │ ├── WMSGetFeatureInfo.html
│ │ │ │ │ ├── WMTSGetFeatureInfo.html
│ │ │ │ │ ├── Zoom.html
│ │ │ │ │ ├── ZoomBox.html
│ │ │ │ │ ├── ZoomIn.html
│ │ │ │ │ ├── ZoomOut.html
│ │ │ │ │ └── ZoomToMaxExtent.html
│ │ │ │ ├── Control.html
│ │ │ │ ├── Events/
│ │ │ │ │ ├── buttonclick.html
│ │ │ │ │ └── featureclick.html
│ │ │ │ ├── Events.html
│ │ │ │ ├── Extras.html
│ │ │ │ ├── Feature/
│ │ │ │ │ └── Vector.html
│ │ │ │ ├── Feature.html
│ │ │ │ ├── Filter/
│ │ │ │ │ ├── Comparison.html
│ │ │ │ │ ├── FeatureId.html
│ │ │ │ │ ├── Logical.html
│ │ │ │ │ └── Spatial.html
│ │ │ │ ├── Filter.html
│ │ │ │ ├── Format/
│ │ │ │ │ ├── ArcXML/
│ │ │ │ │ │ └── Features.html
│ │ │ │ │ ├── ArcXML.html
│ │ │ │ │ ├── Atom.html
│ │ │ │ │ ├── CQL.html
│ │ │ │ │ ├── CSWGetDomain/
│ │ │ │ │ │ ├── v2_0_2.html
│ │ │ │ │ │ └── v2_0_2.js
│ │ │ │ │ ├── CSWGetDomain.html
│ │ │ │ │ ├── CSWGetRecords/
│ │ │ │ │ │ ├── v2_0_2.html
│ │ │ │ │ │ └── v2_0_2.js
│ │ │ │ │ ├── CSWGetRecords.html
│ │ │ │ │ ├── EncodedPolyline.html
│ │ │ │ │ ├── Filter/
│ │ │ │ │ │ ├── v1.html
│ │ │ │ │ │ ├── v1_0_0.html
│ │ │ │ │ │ ├── v1_1_0.html
│ │ │ │ │ │ └── v2_0_0.html
│ │ │ │ │ ├── Filter.html
│ │ │ │ │ ├── GML/
│ │ │ │ │ │ ├── cases.js
│ │ │ │ │ │ ├── v2.html
│ │ │ │ │ │ └── v3.html
│ │ │ │ │ ├── GML.html
│ │ │ │ │ ├── GPX.html
│ │ │ │ │ ├── GeoJSON.html
│ │ │ │ │ ├── GeoRSS.html
│ │ │ │ │ ├── JSON.html
│ │ │ │ │ ├── KML.html
│ │ │ │ │ ├── OGCExceptionReport.html
│ │ │ │ │ ├── OSM.html
│ │ │ │ │ ├── OWSCommon/
│ │ │ │ │ │ ├── v1_0_0.html
│ │ │ │ │ │ └── v1_1_0.html
│ │ │ │ │ ├── OWSContext/
│ │ │ │ │ │ └── v0_3_1.html
│ │ │ │ │ ├── QueryStringFilter.html
│ │ │ │ │ ├── SLD/
│ │ │ │ │ │ ├── v1_0_0.html
│ │ │ │ │ │ └── v1_0_0_GeoServer.html
│ │ │ │ │ ├── SLD.html
│ │ │ │ │ ├── SOSCapabilities/
│ │ │ │ │ │ ├── v1_0_0.html
│ │ │ │ │ │ └── v1_0_0.js
│ │ │ │ │ ├── SOSGetFeatureOfInterest.html
│ │ │ │ │ ├── SOSGetObservation.html
│ │ │ │ │ ├── TMSCapabilities.html
│ │ │ │ │ ├── Text.html
│ │ │ │ │ ├── WCSCapabilities/
│ │ │ │ │ │ └── v1.html
│ │ │ │ │ ├── WCSCapabilities.html
│ │ │ │ │ ├── WCSDescribeCoverage/
│ │ │ │ │ │ └── v1.html
│ │ │ │ │ ├── WCSDescribeCoverage.html
│ │ │ │ │ ├── WCSGetCoverage.html
│ │ │ │ │ ├── WFS.html
│ │ │ │ │ ├── WFSCapabilities/
│ │ │ │ │ │ ├── v1.html
│ │ │ │ │ │ └── v2.html
│ │ │ │ │ ├── WFSCapabilities.html
│ │ │ │ │ ├── WFSDescribeFeatureType.html
│ │ │ │ │ ├── WFST/
│ │ │ │ │ │ ├── v1.html
│ │ │ │ │ │ ├── v1_0_0.html
│ │ │ │ │ │ ├── v1_1_0.html
│ │ │ │ │ │ └── v2_0_0.html
│ │ │ │ │ ├── WFST.html
│ │ │ │ │ ├── WKT.html
│ │ │ │ │ ├── WMC/
│ │ │ │ │ │ ├── v1.html
│ │ │ │ │ │ └── v1_1_0.html
│ │ │ │ │ ├── WMC.html
│ │ │ │ │ ├── WMSCapabilities/
│ │ │ │ │ │ ├── v1_1_1.html
│ │ │ │ │ │ ├── v1_1_1_WMSC.html
│ │ │ │ │ │ └── v1_3_0.html
│ │ │ │ │ ├── WMSCapabilities.html
│ │ │ │ │ ├── WMSDescribeLayer.html
│ │ │ │ │ ├── WMSGetFeatureInfo.html
│ │ │ │ │ ├── WMTSCapabilities/
│ │ │ │ │ │ └── v1_0_0.html
│ │ │ │ │ ├── WMTSCapabilities.html
│ │ │ │ │ ├── WPSCapabilities/
│ │ │ │ │ │ ├── v1_0_0.html
│ │ │ │ │ │ └── v1_0_0.js
│ │ │ │ │ ├── WPSDescribeProcess.html
│ │ │ │ │ ├── WPSExecute.html
│ │ │ │ │ ├── XLS/
│ │ │ │ │ │ └── v1_1_0.html
│ │ │ │ │ ├── XML/
│ │ │ │ │ │ └── VersionedOGC.html
│ │ │ │ │ └── XML.html
│ │ │ │ ├── Format.html
│ │ │ │ ├── Geometry/
│ │ │ │ │ ├── Collection.html
│ │ │ │ │ ├── Curve.html
│ │ │ │ │ ├── LineString.html
│ │ │ │ │ ├── LinearRing.html
│ │ │ │ │ ├── MultiLineString.html
│ │ │ │ │ ├── MultiPoint.html
│ │ │ │ │ ├── MultiPolygon.html
│ │ │ │ │ ├── Point.html
│ │ │ │ │ └── Polygon.html
│ │ │ │ ├── Geometry.html
│ │ │ │ ├── Handler/
│ │ │ │ │ ├── Box.html
│ │ │ │ │ ├── Click.html
│ │ │ │ │ ├── Drag.html
│ │ │ │ │ ├── Feature.html
│ │ │ │ │ ├── Hover.html
│ │ │ │ │ ├── Keyboard.html
│ │ │ │ │ ├── MouseWheel.html
│ │ │ │ │ ├── Path.html
│ │ │ │ │ ├── Pinch.html
│ │ │ │ │ ├── Point.html
│ │ │ │ │ ├── Polygon.html
│ │ │ │ │ └── RegularPolygon.html
│ │ │ │ ├── Handler.html
│ │ │ │ ├── Icon.html
│ │ │ │ ├── Kinetic.html
│ │ │ │ ├── Lang.html
│ │ │ │ ├── Layer/
│ │ │ │ │ ├── ArcGIS93Rest.html
│ │ │ │ │ ├── ArcGISCache.html
│ │ │ │ │ ├── ArcGISCache.json
│ │ │ │ │ ├── ArcIMS.html
│ │ │ │ │ ├── Bing.html
│ │ │ │ │ ├── EventPane.html
│ │ │ │ │ ├── FixedZoomLevels.html
│ │ │ │ │ ├── GeoRSS.html
│ │ │ │ │ ├── Google/
│ │ │ │ │ │ └── v3.html
│ │ │ │ │ ├── Grid.html
│ │ │ │ │ ├── HTTPRequest.html
│ │ │ │ │ ├── Image.html
│ │ │ │ │ ├── KaMap.html
│ │ │ │ │ ├── MapGuide.html
│ │ │ │ │ ├── MapServer.html
│ │ │ │ │ ├── Markers.html
│ │ │ │ │ ├── OSM.html
│ │ │ │ │ ├── PointGrid.html
│ │ │ │ │ ├── PointTrack.html
│ │ │ │ │ ├── SphericalMercator.html
│ │ │ │ │ ├── TMS.html
│ │ │ │ │ ├── Text.html
│ │ │ │ │ ├── TileCache.html
│ │ │ │ │ ├── UTFGrid.html
│ │ │ │ │ ├── Vector/
│ │ │ │ │ │ └── RootContainer.html
│ │ │ │ │ ├── Vector.html
│ │ │ │ │ ├── WMS.html
│ │ │ │ │ ├── WMTS.html
│ │ │ │ │ ├── WrapDateLine.html
│ │ │ │ │ ├── XYZ.html
│ │ │ │ │ ├── atom-1.0.xml
│ │ │ │ │ ├── data_Layer_Text_textfile.txt
│ │ │ │ │ ├── data_Layer_Text_textfile_2.txt
│ │ │ │ │ ├── data_Layer_Text_textfile_overflow.txt
│ │ │ │ │ └── georss.txt
│ │ │ │ ├── Layer.html
│ │ │ │ ├── Map.html
│ │ │ │ ├── Marker/
│ │ │ │ │ └── Box.html
│ │ │ │ ├── Marker.html
│ │ │ │ ├── OLLoader.js
│ │ │ │ ├── OpenLayers1.html
│ │ │ │ ├── OpenLayers2.html
│ │ │ │ ├── OpenLayers3.html
│ │ │ │ ├── OpenLayers4.html
│ │ │ │ ├── OpenLayersJsFiles.html
│ │ │ │ ├── Popup/
│ │ │ │ │ ├── Anchored.html
│ │ │ │ │ └── FramedCloud.html
│ │ │ │ ├── Popup.html
│ │ │ │ ├── Projection.html
│ │ │ │ ├── Protocol/
│ │ │ │ │ ├── CSW.html
│ │ │ │ │ ├── HTTP.html
│ │ │ │ │ ├── SOS.html
│ │ │ │ │ ├── Script.html
│ │ │ │ │ └── WFS.html
│ │ │ │ ├── Protocol.html
│ │ │ │ ├── README.txt
│ │ │ │ ├── Renderer/
│ │ │ │ │ ├── Canvas.html
│ │ │ │ │ ├── Elements.html
│ │ │ │ │ ├── SVG.html
│ │ │ │ │ └── VML.html
│ │ │ │ ├── Renderer.html
│ │ │ │ ├── Request/
│ │ │ │ │ └── XMLHttpRequest.html
│ │ │ │ ├── Request.html
│ │ │ │ ├── Rule.html
│ │ │ │ ├── SingleFile1.html
│ │ │ │ ├── SingleFile2.html
│ │ │ │ ├── SingleFile3.html
│ │ │ │ ├── Strategy/
│ │ │ │ │ ├── BBOX.html
│ │ │ │ │ ├── Cluster.html
│ │ │ │ │ ├── Filter.html
│ │ │ │ │ ├── Fixed.html
│ │ │ │ │ ├── Paging.html
│ │ │ │ │ ├── Refresh.html
│ │ │ │ │ └── Save.html
│ │ │ │ ├── Strategy.html
│ │ │ │ ├── Style.html
│ │ │ │ ├── Style2.html
│ │ │ │ ├── StyleMap.html
│ │ │ │ ├── Symbolizer/
│ │ │ │ │ ├── Line.html
│ │ │ │ │ ├── Point.html
│ │ │ │ │ ├── Polygon.html
│ │ │ │ │ ├── Raster.html
│ │ │ │ │ └── Text.html
│ │ │ │ ├── Symbolizer.html
│ │ │ │ ├── Test.AnotherWay.baseadditions.js
│ │ │ │ ├── Test.AnotherWay.css
│ │ │ │ ├── Test.AnotherWay.geom_eq.js
│ │ │ │ ├── Test.AnotherWay.js
│ │ │ │ ├── Test.AnotherWay.xml_eq.js
│ │ │ │ ├── Tile/
│ │ │ │ │ ├── Image/
│ │ │ │ │ │ └── IFrame.html
│ │ │ │ │ ├── Image.html
│ │ │ │ │ └── UTFGrid.html
│ │ │ │ ├── Tile.html
│ │ │ │ ├── TileManager.html
│ │ │ │ ├── Tween.html
│ │ │ │ ├── Util/
│ │ │ │ │ └── vendorPrefix.html
│ │ │ │ ├── Util.html
│ │ │ │ ├── Util_common.js
│ │ │ │ ├── Util_w3c.html
│ │ │ │ ├── WPSClient.html
│ │ │ │ ├── WPSProcess.html
│ │ │ │ ├── atom-1.0.xml
│ │ │ │ ├── auto-tests.html
│ │ │ │ ├── data_Layer_Text_textfile.txt
│ │ │ │ ├── data_Layer_Text_textfile_2.txt
│ │ │ │ ├── data_Layer_Text_textfile_overflow.txt
│ │ │ │ ├── deprecated/
│ │ │ │ │ ├── Ajax.html
│ │ │ │ │ ├── BaseTypes/
│ │ │ │ │ │ ├── Class.html
│ │ │ │ │ │ └── Element.html
│ │ │ │ │ ├── Control/
│ │ │ │ │ │ └── MouseToolbar.html
│ │ │ │ │ ├── Geometry/
│ │ │ │ │ │ └── Rectangle.html
│ │ │ │ │ ├── Layer/
│ │ │ │ │ │ ├── GML.html
│ │ │ │ │ │ ├── MapServer/
│ │ │ │ │ │ │ └── Untiled.html
│ │ │ │ │ │ ├── MapServer.html
│ │ │ │ │ │ ├── WFS.html
│ │ │ │ │ │ ├── WMS/
│ │ │ │ │ │ │ └── Post.html
│ │ │ │ │ │ ├── WMS.html
│ │ │ │ │ │ ├── Yahoo.html
│ │ │ │ │ │ ├── mice.xml
│ │ │ │ │ │ └── owls.xml
│ │ │ │ │ ├── Popup/
│ │ │ │ │ │ └── AnchoredBubble.html
│ │ │ │ │ ├── Protocol/
│ │ │ │ │ │ ├── SQL/
│ │ │ │ │ │ │ └── Gears.html
│ │ │ │ │ │ └── SQL.html
│ │ │ │ │ ├── Renderer/
│ │ │ │ │ │ └── SVG2.html
│ │ │ │ │ ├── Tile/
│ │ │ │ │ │ └── WFS.html
│ │ │ │ │ └── Util.html
│ │ │ │ ├── georss.txt
│ │ │ │ ├── grid_inittiles.html
│ │ │ │ ├── index.html
│ │ │ │ ├── list-tests.html
│ │ │ │ ├── manual/
│ │ │ │ │ ├── ajax.html
│ │ │ │ │ ├── ajax.txt
│ │ │ │ │ ├── alloverlays-mixed.html
│ │ │ │ │ ├── arcims-2117.html
│ │ │ │ │ ├── arkansas.rss
│ │ │ │ │ ├── big-georss.html
│ │ │ │ │ ├── box-quirks.html
│ │ │ │ │ ├── box-strict.html
│ │ │ │ │ ├── clip-features-svg.html
│ │ │ │ │ ├── dateline-sketch.html
│ │ │ │ │ ├── dateline-smallextent.html
│ │ │ │ │ ├── draw-feature.html
│ │ │ │ │ ├── feature-handler.html
│ │ │ │ │ ├── geodesic.html
│ │ │ │ │ ├── geojson-geomcoll-reprojection.html
│ │ │ │ │ ├── google-fullscreen-overlay.html
│ │ │ │ │ ├── google-panning.html
│ │ │ │ │ ├── google-resize.html
│ │ │ │ │ ├── google-tilt.html
│ │ │ │ │ ├── google-v3-resize.html
│ │ │ │ │ ├── loadend.html
│ │ │ │ │ ├── map-events.html
│ │ │ │ │ ├── memory/
│ │ │ │ │ │ ├── Marker-2258.html
│ │ │ │ │ │ ├── PanZoom-2323.html
│ │ │ │ │ │ ├── RemoveChild-2170.html
│ │ │ │ │ │ └── VML-2170.html
│ │ │ │ │ ├── multiple-google-layers.html
│ │ │ │ │ ├── overviewmap-projection.html
│ │ │ │ │ ├── page-position.html
│ │ │ │ │ ├── pan-redraw-svg.html
│ │ │ │ │ ├── popup-keepInMap.html
│ │ │ │ │ ├── reflow.html
│ │ │ │ │ ├── renderedDimensions.html
│ │ │ │ │ ├── select-feature-right-click.html
│ │ │ │ │ ├── select-feature.html
│ │ │ │ │ ├── tiles-loading.html
│ │ │ │ │ ├── tween.html
│ │ │ │ │ ├── vector-features-performance.html
│ │ │ │ │ └── vector-layer-zindex.html
│ │ │ │ ├── mice.xml
│ │ │ │ ├── node.js/
│ │ │ │ │ ├── mockdom.js
│ │ │ │ │ ├── node-tests.cfg
│ │ │ │ │ ├── node.js
│ │ │ │ │ ├── run-test.js
│ │ │ │ │ └── run.sh
│ │ │ │ ├── owls.xml
│ │ │ │ ├── run-tests.html
│ │ │ │ ├── selenium/
│ │ │ │ │ └── remotecontrol/
│ │ │ │ │ ├── config.cfg
│ │ │ │ │ ├── selenium.py
│ │ │ │ │ ├── setup.txt
│ │ │ │ │ └── test_ol.py
│ │ │ │ ├── speed/
│ │ │ │ │ ├── geometry.html
│ │ │ │ │ ├── string_format.html
│ │ │ │ │ ├── vector-renderers.html
│ │ │ │ │ ├── vector-renderers.js
│ │ │ │ │ ├── wmc_speed.html
│ │ │ │ │ ├── wmscaps.html
│ │ │ │ │ ├── wmscaps.js
│ │ │ │ │ └── wmscaps.xml
│ │ │ │ └── throws.js
│ │ │ ├── theme/
│ │ │ │ └── default/
│ │ │ │ ├── google.css
│ │ │ │ ├── ie6-style.css
│ │ │ │ ├── style.css
│ │ │ │ └── style.mobile.css
│ │ │ └── tools/
│ │ │ ├── BeautifulSoup.py
│ │ │ ├── README.txt
│ │ │ ├── closure_library_jscompiler.py
│ │ │ ├── closure_ws.py
│ │ │ ├── exampleparser.py
│ │ │ ├── jsmin.c
│ │ │ ├── jsmin.py
│ │ │ ├── mergejs.py
│ │ │ ├── minimize.py
│ │ │ ├── oldot.py
│ │ │ ├── release.sh
│ │ │ ├── shrinksafe.py
│ │ │ ├── toposort.py
│ │ │ ├── uglify_js.py
│ │ │ └── update_dev_dir.sh
│ │ ├── css/
│ │ │ ├── ie-only.css
│ │ │ └── tatami.css
│ │ └── vendor/
│ │ ├── backgroundsize.min.htc
│ │ ├── css/
│ │ │ └── bootstrap/
│ │ │ ├── css/
│ │ │ │ └── bootstrap.css
│ │ │ ├── fonts/
│ │ │ │ └── glyphiconshalflings-regular.otf
│ │ │ └── js/
│ │ │ └── bootstrap.js
│ │ └── js/
│ │ ├── marked/
│ │ │ └── marked.js
│ │ └── respond/
│ │ └── respond.js
│ ├── css/
│ │ ├── ie-only.css
│ │ ├── tatami.css
│ │ └── vendor/
│ │ ├── backgroundsize.min.htc
│ │ ├── css/
│ │ │ ├── bootstrap.css
│ │ │ └── jQueryjGrowl.css
│ │ └── fonts/
│ │ └── glyphiconshalflings-regular.otf
│ ├── index.html
│ ├── index.html.tpl
│ ├── index.jsp
│ ├── robots.txt
│ └── sitemap.xml
└── test/
├── java/
│ └── fr/
│ └── ippon/
│ └── tatami/
│ └── web/
│ └── rest/
│ ├── GroupControllerTest.java
│ ├── TagControllerTest.java
│ ├── TimelineControllerTest.java
│ └── UserControllerTest.java
└── javascript/
└── webapp/
└── app/
└── components/
├── about/
│ └── license/
│ └── LicenseController_spec.js
├── account/
│ ├── PasswordModule_spec.js
│ └── preferences/
│ └── Preferences_spec.js
└── home/
└── status/
└── StatusController_spec.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
data
.idea
*.iml
tatami.iml
tatami.ipr
tatami.iws
.classpath
.project
.settings
target
node_modules/
web/node_modules/
src/main/webapp/WEB-INF/generated-wro4j
.merge_file*
.DS_Store
phantomjsdriver.log
*.log
web/src/main/webapp/app/**/*.CONCAT.js
web/src/main/webapp/app/**/*.min.html
web/src/main/webapp/app/**/*.min.js
web/src/main/webapp/app/**/*.MIN.css
web/src/main/webapp/app/**/*.min.css
web/src/main/webapp/css/tatami.min.css
src/main/webapp/app/shared/footer/FooterView.html.sed.del
================================================
FILE: CONTRIBUTING.md
================================================
Contributing to Tatami
=========================
Open Source
------------------
Tatami is an Open Source project, which uses the Apache 2 [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)
license. If you contribute to Tatami, you agree that your code belongs to the Tatami project and follows this license.
Submitting code
------------------
* Create a [GitHub account](https://github.com/signup/free).
* [Submit a ticket for your issue](https://github.com/ippontech/tatami/issues), assuming one does not already exist.
* Clearly describe the issue including steps to reproduce it, when it is a bug.
* Fork the repository on GitHub.
* Make commits of logical units.
* Make sure your commit messages include the ticket number you have created. Github should automatically link
your changes with the ticket.
* Make sure you have added the necessary tests for your changes.
* Run _all_ the tests to assure nothing else was accidentally broken.
* Push your changes in your fork of the repository.
* Submit a pull request to [the repository in the ippontech organization](https://github.com/ippontech/tatami).
* If you need help about making a pull request, [here is the documentation](https://help.github.com/articles/using-pull-requests).
* Your code will be automatically built by [Buildhive](https://buildhive.cloudbees.com/job/ippontech/job/tatami/). Your
pull request should have a Buildhive comment a few minutes after your commit.
================================================
FILE: README.md
================================================
Tatami
================
Presentation
------------------
Tatami is an Open Source enterprise social network.
A public installation of Tatami is provided by [Ippon Technologies](http://www.ippon.fr) at : [https://tatami.ippon.fr](https://tatami.ippon.fr)
Tatami is made with the following technologies :
- HTML5, [AngularJS](https://angularjs.org/) and [Twitter Bootstrap](http://twitter.github.com/bootstrap/)
- [The Spring Framework](http://www.springsource.org/)
- [Apache Cassandra](http://cassandra.apache.org/)
- [Elastic Search](http://www.elasticsearch.org/)
Tatami is developed by [Ippon Technologies](http://www.ippon.fr)
Installation for developers
---------------------------------------
### 5 minute installation
- Clone, fork or download the source code from this Github page
- Install [Maven 3](http://maven.apache.org/)
- Install [npm](https://www.npmjs.com/)
- Point your terminal to the directory you cloned Tatami to.
- `cd web`
- Type `npm install`
- You may need to give root user permissions: `sudo !!`
- `cd ..`
- Run Cassandra with Maven : `mvn cassandra:run`
- Run Jetty from tatami/web with Maven: `mvn jetty:run`
- Connect to the application at http://127.0.0.1:8080
To create users, use the registration form. As we have not configured a SMTP server (you can configure it in src/main/resources/META-INF/tatami/tatami.properties - see below "installation for production use" for more options), the validation URL as well as the password will not be e-mailed to you, but you can see them in the log (look at the Jetty console output).
### Using Tomcat instead of Jetty
If you want to use Tomcat instead of Jetty (which works better in development mode on Windows), just use :
- Run Tomcat from Maven : `mvn tomcat7:run`
### Maven tuning and troubleshooting
If you run into some Permgen or OutOfMemory errors, you can configure your Maven settings accordingly :
```
export MAVEN_OPTS="-XX:PermSize=64m -XX:MaxPermSize=128m -Xms256m -Xmx1024m"
```
If you want to debug remotely the application with your IDE, set up your MAVEN_OPTS :
```
export MAVEN_OPTS="$MAVEN_OPTS -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n"
```
### Cassandra troubleshooting
On Mac OS X, you should use JDK 6 and not JDK 7, see [issue #281](https://github.com/ippontech/tatami/issues/281#issuecomment-12430701).
How to Contribute
---------------------------------------
In order to assure code quality, Ippon manages the pull requests for the project, and upholds certain rules regarding how individuals may contribute.
You may find these rules in your Tatami installation in the file `CONTRIBUTING.md`.
Installation for production use
---------------------------------------
### Cassandra installation
- Download [Apache Cassandra](http://cassandra.apache.org/)
- Install Cassandra : the application will work fine with just one node, but ideally you should have a cluster with at least 3 or 5 nodes
- Cassandra is configured with its cassandra.yaml file : don't forget to backup your "data" and "commitlog" directories
### Tatami installation
In order to use a stable version, use one of the [available tags](https://github.com/ippontech/tatami/tags).
Tatami can be configured with the src/main/resources/META-INF/tatami/tatami.properties file. You can configure this file in 2 ways :
- Edit the file in your own Tatami fork
- Properties in this file are replaced at build time by Maven : you can set up your own Maven profile with your specific properties
Once Tatami is started, you will be able to check your properties at runtime in the Administration page.
To deploy Tatami :
- Create the Tatami WAR file : `mvn package`
- The WAR file will be called "root.war", as Tatami should be run as the root application (on the "/" Web context)
- Deploy the WAR file on your favorite Java EE server
- The WAR has been tested on Jetty 8 and Tomcat 7, and should work fine on all Java EE servers
Upgrading from a previous version
---------------------------------------
Upgrading is normally just a matter of using a newer version of the application.
Sometimes, you will need to update the Cassandra keyspace: upgrade scripts are available in the src/main/cql/upgrade directory.
Launching stress tests
---------------------------------------
Stress tests are done with [Apache JMeter](http://jmeter.apache.org/).
- Launch Cassandra
- Run Tatami from Maven with the `stress-tests` profile : `mvn jetty:run -Pstress-tests`
- Launch JMeter
- Run the `src/test/jmeter/tatami-create-users.jmx` script : it will create 200 normal users, which each has 200 follower users
- Run the stress test : `src/test/jmeter/tatami-stress-test.jmx`
Launching functional tests
---------------------------------------
Functional tests are a work in progress, you do not have to run them in order to use the application.
Requirement : all components must run on localhost :
- for LDAP authentication, the tests starts the LDAP server that the Tatami server will use
- for fixture setup and assertions, the test connects directly to the local cassandra
Launching UI Tests from maven :
- add this profile on your settings.xml :
```xml
tatami
true
C:\path\to\chromedriver.exe
xxxx
xxx@xxx.fr
```
- Run Maven with this command : `mvn clean verify -Puitest`
Launching UI Tests from maven with Chrome :
- install ChromeDriver in your system
- configure the property "webdriver.chrome.driver" in your settings pointing to your chrome driver install directory
- add `-Dgeb.env=chrome` to the maven command above
Launching UI Tests from your IDE :
- Enable a groovy plugin on your IDE
- Activate maven profile "uitest" or add src/integration/* in your classpath
- Run Tatami with Maven : `mvn cassandra:delete cassandra:start jetty:run -Djetty.scanIntervalSeconds=0 -Puitest`
- Run Specs (in src\integration\java\fr\ippon\tatami\uitest) as Junit Tests from your IDE
=> you have to set adequate system properties to your running configurations (the same as those that are necessary in setting.xml for maven : see above)
Thanks
------
Jetbrains is providing us free [Intellij IDEA](http://www.jetbrains.com/idea/) licenses,
which definitely allows us to be more productive and have more fun on the project!
YourKit is kindly supporting open source projects with its full-featured Java Profiler.
YourKit, LLC is the creator of innovative and intelligent tools for profiling
Java and .NET applications. Take a look at YourKit's leading software products:
[YourKit Java Profiler](http://www.yourkit.com/java/profiler/index.jsp) and
[YourKit .NET Profiler](http://www.yourkit.com/.net/profiler/index.jsp).
License
-------
Copyright © 2012-2015 [Ippon Technologies](http://www.ippon.fr)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this application except in compliance with the License.
You may obtain a copy of the License at
[http://www.apache.org/licenses/LICENSE-2.0](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.
================================================
FILE: etc/installation/ubuntu/files/maven/settings.xml
================================================
/opt/tatami/maven/repository
================================================
FILE: etc/installation/ubuntu/install.sh
================================================
#!/bin/sh
#
# description: Installs Tatami on Ubuntu
# This script must be run by the "root" user.
# Run this script directly by typing :
# curl -L https://github.com/ippontech/tatami/raw/master/etc/installation/ubuntu/install.sh | sudo bash
#
# - Tatami is installed in the "/opt/tatami" directory
# - Tatami is run by the "tatami" user
#
echo "Welcome to the Tatami installer"
#################################
# Variables
#################################
echo "Setting up variables"
export USER=tatami
export TATAMI_DIR=/opt/tatami
export MAVEN_VERSION=3.0.4
export JETTY_VERSION=8.1.8.v20121106
#################################
# Install missing packages
#################################
echo "Installing missing packages"
apt-get install git-core curl wget -y --force-yes
#################################
# Install Java
#################################
wget https://github.com/flexiondotorg/oab-java6/raw/0.2.6/oab-java.sh -O oab-java.sh
chmod +x oab-java.sh
sudo ./oab-java.sh
rm oab-java.sh
sudo apt-get install sun-java6-jdk -y
#################################
# Create directories & users
#################################
echo "Creating directories and users"
useradd -m -s /bin/bash $USER
mkdir -p $TATAMI_DIR
mkdir -p $TATAMI_DIR/application
mkdir -p $TATAMI_DIR/maven
mkdir -p $TATAMI_DIR/data
mkdir -p $TATAMI_DIR/data/elasticsearch
mkdir -p $TATAMI_DIR/log
mkdir -p $TATAMI_DIR/log/elasticsearch
#################################
## Download Application
#################################
echo "Getting the application from Github"
cd $TATAMI_DIR/application
git clone https://github.com/ippontech/tatami.git
#################################
## Install Cassandra
#################################
cd $TATAMI_DIR
echo "Installing JNA"
sudo apt-get install libjna-java -y
echo "Configuring OS limits"
cp /etc/security/limits.conf /etc/security/limits.conf.original
echo "* soft nofile 32768" | sudo tee -a /etc/security/limits.conf
echo "* hard nofile 32768" | sudo tee -a /etc/security/limits.conf
echo "root soft nofile 32768" | sudo tee -a /etc/security/limits.conf
echo "root hard nofile 32768" | sudo tee -a /etc/security/limits.conf
echo "* soft nofile 32768" >> /etc/security/limits.conf
echo "* hard nofile 32768" >> /etc/security/limits.conf
echo "root soft nofile 32768" >> /etc/security/limits.conf
echo "root hard nofile 32768" >> /etc/security/limits.conf
echo "* soft memlock unlimited" >> /etc/security/limits.conf
echo "* hard memlock unlimited" >> /etc/security/limits.conf
echo "root soft memlock unlimited" >> /etc/security/limits.conf
echo "root hard memlock unlimited" >> /etc/security/limits.conf
echo "* soft as unlimited" >> /etc/security/limits.conf
echo "* hard as unlimited" >> /etc/security/limits.conf
echo "root soft as unlimited" >> /etc/security/limits.conf
echo "root hard as unlimited" >> /etc/security/limits.conf
sysctl -w vm.max_map_count=131072
sudo swapoff --all
# Cassandra Installation
echo "Installing Cassandra"
echo "deb http://debian.datastax.com/community stable main" >> /etc/apt/sources.list
curl -L http://debian.datastax.com/debian/repo_key | sudo apt-key add -
sudo apt-get update
sudo apt-get install python-cql dsc1.1 -y
sudo apt-get install opscenter-free -y
sudo service opscenterd start
#################################
## Install Jetty
#################################
echo "Installing Jetty"
cd $TATAMI_DIR
sysctl -w net.core.rmem_max=16777216
sysctl -w net.core.wmem_max=16777216
sysctl -w net.ipv4.tcp_rmem="4096 87380 16777216"
sysctl -w net.ipv4.tcp_wmem="4096 16384 16777216"
sysctl -w net.core.somaxconn=4096
sysctl -w net.core.netdev_max_backlog=16384
sysctl -w net.ipv4.tcp_max_syn_backlog=8192
sysctl -w net.ipv4.tcp_syncookies=1
sysctl -w net.ipv4.ip_local_port_range="1024 65535"
sysctl -w net.ipv4.tcp_tw_recycle=1
sysctl -w net.ipv4.tcp_congestion_control=cubic
wget http://central.maven.org/maven2/org/mortbay/jetty/dist/jetty-deb/$JETTY_VERSION/jetty-deb-$JETTY_VERSION.deb
dpkg -i jetty-deb-$JETTY_VERSION.deb
rm -f jetty-deb-$JETTY_VERSION.deb
rm -rf /opt/jetty/webapps/*
#################################
## Install Maven
#################################
echo "Installing Maven"
cd $TATAMI_DIR/maven
wget http://mirrors.linsrv.net/apache/maven/maven-3/$MAVEN_VERSION/binaries/apache-maven-$MAVEN_VERSION-bin.tar.gz
tar -xzf apache-maven-$MAVEN_VERSION-bin.tar.gz
rm -f apache-maven-$MAVEN_VERSION-bin.tar.gz
ln -s $TATAMI_DIR/maven/apache-maven-$MAVEN_VERSION $TATAMI_DIR/maven/current
# Configure Maven for the tatami user
echo "# Begin tatami configuration" >> /home/tatami/.profile
echo "export M2_HOME=/opt/tatami/maven/current" >> /home/tatami/.profile
echo "export PATH=/opt/tatami/maven/current/bin:$PATH" >> /home/tatami/.profile
echo "export MAVEN_OPTS=\"-XX:MaxPermSize=64m -Xms256m -Xmx1024m\"" >> /home/tatami/.profile
echo "# End tatami configuration" >> /home/tatami/.profile
# Configure Maven repository
mkdir -p $TATAMI_DIR/maven/repository
cp $TATAMI_DIR/application/tatami/etc/installation/ubuntu/files/maven/settings.xml $TATAMI_DIR/maven/apache-maven-$MAVEN_VERSION/conf
#################################
## Install & run Application
#################################
chown -R $USER $TATAMI_DIR
./update.sh
#################################
## Post install
#################################
================================================
FILE: etc/installation/ubuntu/uninstall.sh
================================================
#!/bin/sh
#
# description: Uninstalls Tatami on Ubuntu
# This script must be run by the "root" user.
#
# Run this script directly by typing :
# curl -L https://github.com/ippontech/tatami/raw/master/etc/installation/ubuntu/uninstall.sh | sudo bash
#
# - Deletes the "/opt/tatami"
# - Deletes the "tatami" user
echo "Tatami uninstaller"
mv /etc/security/limits.conf.original /etc/security/limits.conf
userdel -f -r tatami
echo "Delete Tatami directory"
rm -rf /opt/tatami
================================================
FILE: etc/installation/ubuntu/update.sh
================================================
#!/bin/sh
#
# description: Updates Tatami from Git
# This script must be run by the "tatami" user, who must be a sudoer.
echo "Welcome to the Tatami updater"
#################################
# Variables
#################################
echo "Setting up variables"
export USER=tatami
export TATAMI_DIR=/opt/tatami
#################################
# Update application
#################################
cd $TATAMI_DIR/application/tatami
git pull
cd /opt/tatami/application/tatami && mvn -Pprod -DskipTests clean package
sudo /etc/init.d/jetty stop
sudo cp /opt/tatami/application/tatami/target/root.war /opt/jetty/webapps/root.war
sudo /etc/init.d/jetty start
================================================
FILE: jenkinsScripts/insertGoogleAuthKeys.sh
================================================
#!/bin/bash
#Insert google auth variables into the build.
googleKey=$1
googleSecret=$2
newServer=$3
usage='startTatami '
if [ "$#" -ne 3 ]; then
echo "Need 3 paremeters"
echo "$usage"
exit 1
fi
replaceKey='${tatami.google.clientId}'
replaceSecret='${tatami.google.clientSecret}'
oldServer="http:\/\/localhost:8080<\/tatami.url>"
find ../src -name *security.xml | xargs sed -i "s/$replaceSecret/$googleSecret/g"
find ../src -name *security.xml | xargs sed -i "s/$replaceKey/$googleKey/g"
sed -i "s/$oldServer/$newServer/g" ../pom.xml
================================================
FILE: jenkinsScripts/restoreDatabase.sh
================================================
#!/bin/bash
rm -rf ../target/cassandra || true
rm -rf ./target/elasticsearch || true
tar -zxvf save.tar.gz -C ../target/
tar -zxvf db.tar.gz -C ./target/
rm -f save.tar.gz || true
================================================
FILE: jenkinsScripts/saveDatabase.sh
================================================
#!/bin/bash
tar -zcvf save.tar.gz ../target/cassandra ../target/elasticsearch
tar -zcvf db.tar.gz ./target/elasticsearch
================================================
FILE: jenkinsScripts/startTatami.sh
================================================
#!/bin/bash
#Insert google Authentication keys.
./insertGoogleAuthKeys.sh $1 $2 $3
if [ $? -ne 0 ]; then
exit 1
fi
#Start cassandra
mvn -f ../pom.xml cassandra:run 2> cassandra.error >cassandra.log &
echo "$!" >tatamiPID
#start jetty
mvn -f ../pom.xml jetty:run 2> jetty.error >jetty.log &
echo "$!" >> tatamiPID
================================================
FILE: jenkinsScripts/stopTatami.sh
================================================
#!/bin/bash
cat tatamiPID | xargs kill || true
rm tatamiPID || true
================================================
FILE: mobile/.bowerrc
================================================
{
"directory": "www/lib"
}
================================================
FILE: mobile/.editorconfig
================================================
# http://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 4
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
insert_final_newline = false
trim_trailing_whitespace = false
================================================
FILE: mobile/.gitignore
================================================
# Specifies intentionally untracked files to ignore when using Git
# http://git-scm.com/docs/gitignore
www/lib/
node_modules/
platforms/
plugins/
================================================
FILE: mobile/README.md
================================================
Tatami Mobile Beta
==================
If interested in the mobile beta, follow these steps:
Prepare Project
---------------
- Clone, fork or download the source code from this Github page
- Install [Maven](http://maven.apache.org/)
- Install [Ionic](http://ionicframework.com/)
- `npm install -g cordova ionic`
- Point your terminal to the directory you cloned Tatami to.
- `cd mobile`
- Run Maven : `mvn -Pmobile-prod install`
Deploy to Device
----------------
### iOS
- Open Xcode
- Open `tatami/mobile/platforms/ios/Tatami.xcodeproj`
- Connect device
- Click play on top left
### Android
- Connect device
- Run the following command
- `ionic build android && ionic run android`
================================================
FILE: mobile/bower.json
================================================
{
"name": "tatami",
"private": "true",
"devDependencies": {
"ionic": "~1.2.4",
"angular-mocks": "1.4.3",
"angular-marked": "~1.0.1",
"angular-animate": "~1.5.3",
"angular-sanitize": "~1.5.3",
"angular-resource": "~1.5.3",
"ngCordova": "~0.1.24-alpha",
"angular-translate": "~2.11.0",
"angular-translate-interpolation-messageformat": "~2.11.0",
"angular-translate-loader-partial": "~2.11.0",
"messageformat": "~0.3.1"
},
"resolutions": {
"angular-sanitize": "~1.5.3",
"angular-animate": "~1.5.3",
"ionic-toast": "v0.2.0"
},
"dependencies": {
"ionic-toast": "^0.4.1"
}
}
================================================
FILE: mobile/config.xml
================================================
Tatami
The Tatami mobile app is designed to access the Tatami API, and it is an open source, enterprise social media platform.
Ippon Technology, Inc
================================================
FILE: mobile/gulpfile.js
================================================
var gulp = require('gulp');
var gutil = require('gulp-util');
var bower = require('bower');
var concat = require('gulp-concat');
var sass = require('gulp-sass');
var minifyCss = require('gulp-minify-css');
var rename = require('gulp-rename');
var sh = require('shelljs');
var replace = require('replace');
var paths = {
sass: ['./scss/**/*.scss'],
css: ['./www/css/*.css']
};
gulp.task('default', ['sass', 'css']);
gulp.task('sass', function(done) {
gulp.src('./scss/ionic.app.scss')
.pipe(sass())
.on('error', sass.logError)
.pipe(gulp.dest('./www/css/'))
.pipe(minifyCss({
keepSpecialComments: 0
}))
.pipe(rename({ extname: '.min.css' }))
.pipe(gulp.dest('./www/css/'))
.on('end', done);
});
gulp.task('css', function(done) {
gulp.src('./www/css/style.css')
.pipe(minifyCss({
keepSpecialComments: 0
}))
.pipe(rename({ extname: '.min.css' }))
.pipe(gulp.dest('./www/css/'))
.on('end', done);
});
gulp.task('watch', function() {
gulp.watch(paths.sass, ['sass']);
gulp.watch(paths.css, ['css']);
});
gulp.task('install', ['git-check'], function() {
return bower.commands.install()
.on('log', function(data) {
gutil.log('bower', gutil.colors.cyan(data.id), data.message);
});
});
gulp.task('git-check', function(done) {
if (!sh.which('git')) {
console.log(
' ' + gutil.colors.red('Git is not installed.'),
'\n Git, the version control system, is required to download Ionic.',
'\n Download git here:', gutil.colors.cyan('http://git-scm.com/downloads') + '.',
'\n Once git is installed, run \'' + gutil.colors.cyan('gulp install') + '\' again.'
);
process.exit(1);
}
done();
});
var replaceFiles = ['./www/app/tatami.endpoint.js'];
gulp.task('dev', function() {
return replace({
regex: 'http://tatami.ippon.fr|http://10.1.10.202:8100',
replacement: 'http://localhost:8100',
paths: replaceFiles,
recursive: false,
silent: false
});
});
gulp.task('device-dev', function() {
return replace({
regex: 'http://localhost:8100|http://tatami.ippon.fr',
replacement: 'http://10.1.10.202:8100',
paths: replaceFiles,
recursive: false,
silent: false
});
});
gulp.task('prod', function() {
return replace({
regex: 'http://localhost:8100|http://10.1.10.202:8100',
replacement: 'http://tatami.ippon.fr',
paths: replaceFiles,
recursive: false,
silent: false
});
});
================================================
FILE: mobile/hooks/README.md
================================================
# Cordova Hooks
This directory may contain scripts used to customize cordova commands. This
directory used to exist at `.cordova/hooks`, but has now been moved to the
project root. Any scripts you add to these directories will be executed before
and after the commands corresponding to the directory name. Useful for
integrating your own build systems or integrating with version control systems.
__Remember__: Make your scripts executable.
## Hook Directories
The following subdirectories will be used for hooks:
after_build/
after_compile/
after_docs/
after_emulate/
after_platform_add/
after_platform_rm/
after_platform_ls/
after_plugin_add/
after_plugin_ls/
after_plugin_rm/
after_plugin_search/
after_prepare/
after_run/
after_serve/
before_build/
before_compile/
before_docs/
before_emulate/
before_platform_add/
before_platform_rm/
before_platform_ls/
before_plugin_add/
before_plugin_ls/
before_plugin_rm/
before_plugin_search/
before_prepare/
before_run/
before_serve/
pre_package/ <-- Windows 8 and Windows Phone only.
## Script Interface
All scripts are run from the project's root directory and have the root directory passes as the first argument. All other options are passed to the script using environment variables:
* CORDOVA_VERSION - The version of the Cordova-CLI.
* CORDOVA_PLATFORMS - Comma separated list of platforms that the command applies to (e.g.: android, ios).
* CORDOVA_PLUGINS - Comma separated list of plugin IDs that the command applies to (e.g.: org.apache.cordova.file, org.apache.cordova.file-transfer)
* CORDOVA_HOOK - Path to the hook that is being executed.
* CORDOVA_CMDLINE - The exact command-line arguments passed to cordova (e.g.: cordova run ios --emulate)
If a script returns a non-zero exit code, then the parent cordova command will be aborted.
## Writing hooks
We highly recommend writting your hooks using Node.js so that they are
cross-platform. Some good examples are shown here:
[http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/](http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/)
================================================
FILE: mobile/hooks/after_prepare/010_add_platform_class.js
================================================
#!/usr/bin/env node
// Add Platform Class
// v1.0
// Automatically adds the platform class to the body tag
// after the `prepare` command. By placing the platform CSS classes
// directly in the HTML built for the platform, it speeds up
// rendering the correct layout/style for the specific platform
// instead of waiting for the JS to figure out the correct classes.
var fs = require('fs');
var path = require('path');
var rootdir = process.argv[2];
function addPlatformBodyTag(indexPath, platform) {
// add the platform class to the body tag
try {
var platformClass = 'platform-' + platform;
var cordovaClass = 'platform-cordova platform-webview';
var html = fs.readFileSync(indexPath, 'utf8');
var bodyTag = findBodyTag(html);
if(!bodyTag) return; // no opening body tag, something's wrong
if(bodyTag.indexOf(platformClass) > -1) return; // already added
var newBodyTag = bodyTag;
var classAttr = findClassAttr(bodyTag);
if(classAttr) {
// body tag has existing class attribute, add the classname
var endingQuote = classAttr.substring(classAttr.length-1);
var newClassAttr = classAttr.substring(0, classAttr.length-1);
newClassAttr += ' ' + platformClass + ' ' + cordovaClass + endingQuote;
newBodyTag = bodyTag.replace(classAttr, newClassAttr);
} else {
// add class attribute to the body tag
newBodyTag = bodyTag.replace('>', ' class="' + platformClass + ' ' + cordovaClass + '">');
}
html = html.replace(bodyTag, newBodyTag);
fs.writeFileSync(indexPath, html, 'utf8');
process.stdout.write('add to body class: ' + platformClass + '\n');
} catch(e) {
process.stdout.write(e);
}
}
function findBodyTag(html) {
// get the body tag
try{
return html.match(/])(.*?)>/gi)[0];
}catch(e){}
}
function findClassAttr(bodyTag) {
// get the body tag's class attribute
try{
return bodyTag.match(/ class=["|'](.*?)["|']/gi)[0];
}catch(e){}
}
if (rootdir) {
// go through each of the platform directories that have been prepared
var platforms = (process.env.CORDOVA_PLATFORMS ? process.env.CORDOVA_PLATFORMS.split(',') : []);
for(var x=0; x
tatami
fr.ippon.tatami
4.0.5
4.0.0
mobile
fr.ippon.tatami
services
4.0.5
fr.ippon.tatami
services
4.0.5
test-jar
test
dev
org.codehaus.mojo
exec-maven-plugin
1.2.1
gulp
install
exec
${project.npm.bin}/gulp
dev
ionic
install
exec
ionic
serve
-l
device-dev
org.codehaus.mojo
exec-maven-plugin
1.2.1
install
exec
${project.npm.bin}/gulp
device-dev
mobile-prod
org.codehaus.mojo
exec-maven-plugin
1.2.1
gulp
install
exec
${project.npm.bin}/gulp
prod
ionic
install
exec
ionic
build
tatami-mobile-${project.version}
maven-clean-plugin
2.6.1
${project.basedir}/node_modules/
false
org.codehaus.mojo
exec-maven-plugin
1.2.1
install-npm-mobile-dependencies
generate-sources
npm
install
exec
com.kelveden
maven-karma-plugin
1.6
test
start
${project.basedir}/node_modules/.bin/karma
${project.basedir}/karma.ci.conf.js
================================================
FILE: mobile/scss/ionic.app.scss
================================================
/*
To customize the look and feel of Ionic, you can override the variables
in ionic's _variables.scss file.
For example, you might change some of the default colors:
$light: #fff !default;
$stable: #f8f8f8 !default;
$positive: #387ef5 !default;
$calm: #11c1f3 !default;
$balanced: #33cd5f !default;
$energized: #ffc900 !default;
$assertive: #ef473a !default;
$royal: #886aea !default;
$dark: #444 !default;
*/
// The path for our ionicons font files, relative to the built CSS in www/css
$ionicons-font-path: "../lib/ionic/release/fonts" !default;
// Include all of Ionic
@import "../www/lib/ionic/release/css/ionic";
.platform-ios {
.tatami-location {
@extend .ion-ios-location;
}
}
.platform-android {
.tatami-location {
@extend .ion-android-locate;
}
}
.item-image i:last-child {
position: absolute;
border-radius: 50%;
width: 20px;
height: 20px;
background-color: black;
top: 15px;
right: 15px;
}
.link-span {
cursor: pointer;
color: #387ef5;
}
.center {
margin-left: auto;
margin-right: auto;
display: block;
}
p ul {
list-style-type: disc !important;
list-style-position: inside !important;
}
//#list ul {
// margin-top: 30px;
//}
//#list ul li {
// text-align: left;
// list-style: disc;
// margin: 10px 0px;
//}
================================================
FILE: mobile/www/app/components/follow/follow.html
================================================
================================================
FILE: mobile/www/app/components/follow/follow.js
================================================
(function() {
'use strict';
angular.module('tatami')
.config(followConfig);
followConfig.$inject = ['$stateProvider'];
function followConfig($stateProvider) {
$stateProvider
.state('follow', {
url: '/follow',
parent: 'tatami',
abstract: true,
templateUrl: 'app/components/follow/follow.html',
resolve: {
currentUser: getCurrentUser,
translatePartialLoader: getTranslatePartialLoader
}
});
getCurrentUser.$inject = ['ProfileService'];
function getCurrentUser(ProfileService) {
return ProfileService.get().$promise;
}
getTranslatePartialLoader.$inject = ['$translate', '$translatePartialLoader'];
function getTranslatePartialLoader($translate, $translatePartialLoader) {
$translatePartialLoader.addPart('follow');
return $translate.refresh();
}
}
})();
================================================
FILE: mobile/www/app/components/follow/follower/follower.controller.js
================================================
(function() {
'use strict';
angular.module('tatami')
.controller('FollowerCtrl', followerCtrl);
followerCtrl.$inject = ['followers', 'TatamiUserRefresherService', 'currentUser'];
function followerCtrl(followers, TatamiUserRefresherService, currentUser) {
var vm = this;
vm.followers = followers;
vm.getNewFollowers = getNewFollowers;
function getNewFollowers() {
TatamiUserRefresherService.refreshFollowers(currentUser).then(setUsers);
}
setUsers.$inject = ['followers'];
function setUsers(followers) {
vm.followers = followers;
}
}
})();
================================================
FILE: mobile/www/app/components/follow/follower/follower.html
================================================
================================================
FILE: mobile/www/app/components/follow/follower/follower.js
================================================
(function() {
'use strict';
angular.module('tatami')
.config(config);
config.$inject = ['$stateProvider'];
function config($stateProvider) {
$stateProvider
.state('follower', {
url: '/follower',
parent: 'follow',
views: {
'follower': {
templateUrl: 'app/components/follow/follower/follower.html',
controller: 'FollowerCtrl',
controllerAs: 'vm'
}
},
resolve: {
followers: followers
}
});
}
followers.$inject = ['UserService', 'currentUser'];
function followers(UserService, currentUser) {
return UserService.getFollowers({ username: currentUser.username }).$promise;
}
angular.module('tatami')
.run(run);
run.$inject = ['TatamiState'];
function run(TatamiState) {
TatamiState.addProfileState('follower', 'follow');
TatamiState.addConversationState('follower', 'follow');
TatamiState.addTagState('follower', 'follow');
}
})();
================================================
FILE: mobile/www/app/components/follow/following/following.controller.js
================================================
(function() {
'use strict';
angular.module('tatami')
.controller('FollowingCtrl', FollowingCtrl);
FollowingCtrl.$inject = ['following', 'TatamiUserRefresherService', 'currentUser'];
function FollowingCtrl(following, TatamiUserRefresherService, currentUser) {
var vm = this;
vm.following = following;
vm.getNewFollowing = getNewFollowing;
function getNewFollowing() {
TatamiUserRefresherService.refreshFollowing(currentUser).then(setUsers);
}
setUsers.$inject = ['following'];
function setUsers(following) {
vm.following = following;
}
}
})();
================================================
FILE: mobile/www/app/components/follow/following/following.html
================================================
================================================
FILE: mobile/www/app/components/follow/following/following.js
================================================
(function() {
'use strict';
angular.module('tatami')
.config(config);
config.$inject = ['$stateProvider'];
function config($stateProvider) {
$stateProvider
.state('following', {
url: '/following',
parent: 'follow',
views: {
'following': {
templateUrl: 'app/components/follow/following/following.html',
controller: 'FollowingCtrl',
controllerAs: 'vm'
}
},
resolve: {
following: getFollowing
}
});
}
getFollowing.$inject = ['UserService', 'currentUser'];
function getFollowing(UserService, currentUser) {
return UserService.getFollowing({ username: currentUser.username }).$promise;
}
angular.module('tatami')
.run(run);
run.$inject = ['TatamiState'];
function run(TatamiState) {
TatamiState.addProfileState('following', 'follow');
TatamiState.addConversationState('following', 'follow');
TatamiState.addTagState('following', 'follow');
}
})();
================================================
FILE: mobile/www/app/components/follow/suggested/suggested.controller.js
================================================
(function() {
'use strict';
angular.module('tatami')
.controller('SuggestedCtrl', suggestedCtrl);
suggestedCtrl.$inject = ['suggested', 'TatamiUserRefresherService'];
function suggestedCtrl(suggested, TatamiUserRefresherService) {
var vm = this;
vm.suggested = suggested;
vm.getNewSuggested = getNewSuggested;
function getNewSuggested() {
TatamiUserRefresherService.refreshSuggested().$promise.then(updateUsers);
}
updateUsers.$inject = ['suggested'];
function updateUsers(suggested) {
vm.suggested = suggested;
}
}
})();
================================================
FILE: mobile/www/app/components/follow/suggested/suggested.html
================================================
================================================
FILE: mobile/www/app/components/follow/suggested/suggested.js
================================================
(function() {
'use strict';
angular.module('tatami')
.config(config);
config.$inject = ['$stateProvider'];
function config($stateProvider) {
$stateProvider
.state('suggested', {
url: '/suggested',
parent: 'follow',
views: {
'suggested': {
templateUrl: 'app/components/follow/suggested/suggested.html',
controller: 'SuggestedCtrl',
controllerAs: 'vm'
}
},
resolve: {
suggested: getSuggested
}
});
}
getSuggested.$inject = ['UserService'];
function getSuggested(UserService) {
return UserService.getSuggestions().$promise;
}
angular.module('tatami')
.run(run);
run.$inject = ['TatamiState'];
function run(TatamiState) {
TatamiState.addProfileState('suggested', 'follow');
TatamiState.addConversationState('suggested', 'follow');
TatamiState.addTagState('suggested', 'follow');
}
})();
================================================
FILE: mobile/www/app/components/home/favorites/favorites.controller.js
================================================
(function() {
'use strict';
angular.module('tatami')
.controller('FavoritesCtrl', favoritesCtrl);
favoritesCtrl.$inject = ['favorites', 'currentUser', 'TatamiStatusRefresherService', '$q'];
function favoritesCtrl(favorites, currentUser, TatamiStatusRefresherService, $q) {
var vm = this;
vm.favorites = favorites;
vm.currentUser = currentUser;
vm.getNewStatuses = getNewStatuses;
vm.getEmpty = getEmpty;
function getNewStatuses() {
return TatamiStatusRefresherService.refreshFavorites();
}
getEmpty.$inject = ['finalStatus'];
function getEmpty(finalStatus) {
var deferred = $q.defer();
deferred.resolve([]);
return deferred.promise;
}
}
})();
================================================
FILE: mobile/www/app/components/home/favorites/favorites.html
================================================
================================================
FILE: mobile/www/app/components/home/favorites/favorites.js
================================================
(function() {
'use strict';
angular.module('tatami')
.config(config);
config.$inject = ['$stateProvider'];
function config($stateProvider) {
$stateProvider
.state('favorites', {
url: '/favorites',
parent: 'home',
views: {
'favorites': {
templateUrl: 'app/components/home/favorites/favorites.html',
controller: 'FavoritesCtrl',
controllerAs: 'vm'
}
},
resolve: {
favorites: favorites
}
});
favorites.$inject = ['HomeService'];
function favorites(HomeService) {
return HomeService.getFavorites().$promise;
}
}
angular.module('tatami')
.run(run);
run.$inject = ['TatamiState'];
function run(TatamiState) {
TatamiState.addProfileState('favorites', 'home');
TatamiState.addConversationState('favorites', 'home');
TatamiState.addTagState('favorites', 'home');
}
})();
================================================
FILE: mobile/www/app/components/home/home.html
================================================
================================================
FILE: mobile/www/app/components/home/home.js
================================================
(function() {
'use strict';
angular.module('tatami')
.config(homeConfig);
homeConfig.$inject = ['$stateProvider'];
function homeConfig($stateProvider) {
$stateProvider
.state('home', {
parent: 'tatami',
abstract: true,
url: '/home',
templateUrl: 'app/components/home/home.html',
resolve: {
currentUser: getCurrentUser,
translatePartialLoader: getTranslatePartialLoader
}
})
}
getCurrentUser.$inject = ['ProfileService'];
function getCurrentUser(ProfileService) {
return ProfileService.get().$promise.then(function(currentUser) {
console.log(currentUser);
return currentUser;
});
}
getTranslatePartialLoader.$inject = ['$translate', '$translatePartialLoader'];
function getTranslatePartialLoader($translate, $translatePartialLoader) {
$translatePartialLoader.addPart('home');
return $translate.refresh();
}
})();
================================================
FILE: mobile/www/app/components/home/mentions/mentions.controller.js
================================================
(function() {
'use strict';
angular.module('tatami')
.controller('MentionsCtrl', mentionsCtrl);
mentionsCtrl.$inject = ['mentioned', 'currentUser', 'TatamiStatusRefresherService'];
function mentionsCtrl(mentioned, currentUser, TatamiStatusRefresherService) {
var vm = this;
vm.mentioned = mentioned;
vm.currentUser = currentUser;
vm.remove = remove;
vm.getNewStatuses = getNewStatuses;
vm.getOldStatuses = getOldStatuses;
remove.$inject = ['mention'];
function remove(mention) {
vm.mentioned.splice(vm.mentioned.indexOf(mention), 1);
}
function getNewStatuses() {
return TatamiStatusRefresherService.refreshMentions();
}
getOldStatuses.$inject = ['finalStatus'];
function getOldStatuses(finalStatus) {
return TatamiStatusRefresherService.getOldMentions(finalStatus);
}
}
})();
================================================
FILE: mobile/www/app/components/home/mentions/mentions.html
================================================
================================================
FILE: mobile/www/app/components/home/mentions/mentions.js
================================================
(function() {
'use strict';
angular.module('tatami')
.config(config);
config.$inject = ['$stateProvider'];
function config($stateProvider) {
$stateProvider
.state('mentions', {
url: '/mentions',
parent: 'home',
views: {
'mentions': {
templateUrl: 'app/components/home/mentions/mentions.html',
controller: 'MentionsCtrl',
controllerAs: 'vm'
}
},
resolve: {
mentioned: mentioned
}
});
}
angular.module('tatami')
.run(run);
run.$inject = ['TatamiState'];
function run(TatamiState) {
TatamiState.addProfileState('mentions', 'home');
TatamiState.addConversationState('mentions', 'home');
TatamiState.addTagState('mentions', 'home');
}
mentioned.$inject = ['HomeService'];
function mentioned(HomeService) {
return HomeService.getMentions().$promise;
}
})();
================================================
FILE: mobile/www/app/components/home/more/all_users/all.users.controller.js
================================================
(function() {
'use strict';
angular.module('tatami')
.controller('AllUsersController', allUsersController);
allUsersController.$inject = ['currentUser', 'TatamiUserRefresherService', '$scope', '$timeout'];
function allUsersController(currentUser, TatamiUserRefresherService, $scope, $timeout) {
var vm = this;
vm.currentUser = currentUser;
vm.users = [];
vm.isFinished = false;
vm.getNextUsers = getNextUsers;
function getNextUsers() {
return TatamiUserRefresherService.getNextUsers(vm.users.length).then(addUsers);
}
addUsers.$inject = ['users'];
function addUsers(nextUsers) {
$timeout(function() {
$scope.$apply(function() {
vm.users.push.apply(vm.users, nextUsers);
vm.isFinished = nextUsers.length === 0;
});
})
}
}
})();
================================================
FILE: mobile/www/app/components/home/more/all_users/all.users.html
================================================
================================================
FILE: mobile/www/app/components/home/more/all_users/all.users.js
================================================
(function() {
'use strict';
angular.module('tatami')
.config(config);
config.$inject = ['$stateProvider'];
function config($stateProvider) {
$stateProvider
.state('allusers', {
url: '/allusers',
parent: 'more',
views: {
'more@home': {
templateUrl: 'app/components/home/more/all_users/all.users.html',
controller: 'AllUsersController',
controllerAs: 'vm'
}
}
});
}
angular.module('tatami')
.run(run);
run.$inject = ['TatamiState'];
function run(TatamiState) {
TatamiState.addProfileState('allusers', 'home');
TatamiState.addConversationState('allusers', 'home');
TatamiState.addTagState('allusers', 'home');
}
})();
================================================
FILE: mobile/www/app/components/home/more/blocked_users/blocked.users.controller.js
================================================
(function() {
'use strict';
angular.module('tatami')
.controller('BlockedUsersController', blockedUsersController);
blockedUsersController.$inject = ['$scope', 'currentUser', 'BlockService'];
function blockedUsersController($scope, currentUser, BlockService) {
var vm = this;
vm.currentUser = currentUser;
vm.blockedUsers = [];
vm.updateUser = updateUser;
vm.getBlockedUsersForUser = getBlockedUsersForUser;
vm.hasBlockedUsers = hasBlockedUsers;
function updateUser() {
BlockService.updateBlockedUser(
{username: vm.status.username}
);
}
function getBlockedUsersForUser() {
BlockService.getBlockedUsersForUser(
{username: vm.currentUser.username},
function (response) {
vm.blockedUsers = response;
}
);
$scope.$broadcast('scroll.refreshComplete');
}
function hasBlockedUsers() {
return vm.blockedUsers.length>0;
}
}
})();
================================================
FILE: mobile/www/app/components/home/more/blocked_users/blocked.users.html
================================================
================================================
FILE: mobile/www/app/components/home/more/blocked_users/blocked.users.js
================================================
(function() {
'use strict';
angular.module('tatami')
.config(config);
config.$inject = ['$stateProvider'];
function config($stateProvider) {
$stateProvider
.state('blockedusers', {
url: '/blockedusers',
parent: 'more',
views: {
'more@home': {
templateUrl: 'app/components/home/more/blocked_users/blocked.users.html',
controller: 'BlockedUsersController',
controllerAs: 'vm'
}
}
});
}
angular.module('tatami')
.run(run);
run.$inject = ['TatamiState'];
function run(TatamiState) {
TatamiState.addProfileState('blockedusers', 'home');
TatamiState.addConversationState('blockedusers', 'home');
TatamiState.addTagState('blockedusers', 'home');
}
})();
================================================
FILE: mobile/www/app/components/home/more/company/company-timeline.html
================================================
================================================
FILE: mobile/www/app/components/home/more/company/company.timeline.controller.js
================================================
(function() {
'use strict';
angular.module('tatami')
.controller('CompanyTimelineCtrl', companyTimelineCtrl);
companyTimelineCtrl.$inject = ['statuses', 'currentUser', 'TatamiStatusRefresherService'];
function companyTimelineCtrl(statuses, currentUser, TatamiStatusRefresherService) {
var vm = this;
vm.statuses = statuses;
vm.currentUser = currentUser;
vm.getNewStatuses = getNewStatuses;
vm.getOldStatuses = getOldStatuses;
function getNewStatuses() {
return TatamiStatusRefresherService.refreshCompanyTimeline();
}
getOldStatuses.$inject = ['finalStatus'];
function getOldStatuses(finalStatus) {
return TatamiStatusRefresherService.getOldFromCompanyTimeline(finalStatus);
}
}
})();
================================================
FILE: mobile/www/app/components/home/more/company/company.timeline.js
================================================
(function() {
'use strict';
angular.module('tatami')
.config(config);
config.$inject = ['$stateProvider'];
function config($stateProvider) {
$stateProvider
.state('company', {
url: '/company/timeline',
parent: 'more',
views: {
'more@home': {
templateUrl: 'app/components/home/more/company/company-timeline.html',
controller: 'CompanyTimelineCtrl',
controllerAs: 'vm'
}
},
resolve: {
statuses: getStatuses
}
});
getStatuses.$inject = ['HomeService'];
function getStatuses(HomeService) {
return HomeService.getCompanyTimeline().$promise;
}
}
angular.module('tatami')
.run(run);
run.$inject = ['TatamiState'];
function run(TatamiState) {
TatamiState.addProfileState('company', 'home');
TatamiState.addConversationState('company', 'home');
TatamiState.addTagState('company', 'home');
}
})();
================================================
FILE: mobile/www/app/components/home/more/more.controller.js
================================================
(function() {
'use strict';
angular.module('tatami')
.controller('MoreController', moreController);
moreController.$inject = [
'$state',
'$localStorage',
'currentUser'
];
function moreController($state, $localStorage, currentUser) {
var vm = this;
vm.currentUser = currentUser;
vm.logout = logout;
vm.goToCompanyTimeline = goToCompanyTimeline;
vm.goToSettings = goToSettings;
vm.goToBlockedUsers = goToBlockedUsers;
vm.goToAllUsers = goToAllUsers;
vm.goToReportedStatus = goToReportedStatus;
function logout() {
$localStorage.signOut();
$state.go('login');
}
function goToCompanyTimeline() {
$state.go('company');
}
function goToSettings() {
$state.go('settings');
}
function goToBlockedUsers(){
$state.go('blockedusers');
}
function goToReportedStatus(){
$state.go('reportedStatus')
}
function goToAllUsers() {
$state.go('allusers')
}
}
})();
================================================
FILE: mobile/www/app/components/home/more/more.html
================================================
================================================
FILE: mobile/www/app/components/home/more/more.js
================================================
(function() {
'use strict';
angular.module('tatami')
.config(moreConfig);
moreConfig.$inject = ['$stateProvider'];
function moreConfig($stateProvider) {
$stateProvider
.state('more', {
url: '/more',
parent: 'home',
views: {
'more': {
templateUrl: 'app/components/home/more/more.html',
controller: 'MoreController',
controllerAs: 'vm'
}
},
resolve: {
translatePartialLoader: getTranslatePartialLoader
}
});
getTranslatePartialLoader.$inject = ['$translate', '$translatePartialLoader'];
function getTranslatePartialLoader($translate, $translatePartialLoader) {
$translatePartialLoader.addPart('more');
return $translate.refresh();
}
}
angular.module('tatami')
.run(run);
run.$inject = ['TatamiState'];
function run(TatamiState) {
TatamiState.addProfileState('more', 'home');
}
})();
================================================
FILE: mobile/www/app/components/home/more/reportedStatus/reportedStatus.controller.js
================================================
/**
* Created by emilyklein on 7/11/16.
*/
(function() {
'use strict';
angular.module('tatami')
.controller('ReportedStatusController', reportedStatusController);
reportedStatusController.$inject = ['$state', '$scope', 'currentUser', 'ReportService', '$ionicPopup', '$translate', 'ToastService'];
function reportedStatusController($state, $scope, currentUser, ReportService, $ionicPopup, $translate, ToastService) {
var vm = this;
vm.reportedStatuses = [];
vm.currentUser = currentUser;
vm.getReportedStatuses = getReportedStatuses;
vm.hasReportedStatus = hasReportedStatus;
vm.approveStatus = approveStatus;
vm.deleteStatus = deleteStatus;
function reportStatus() {
ReportService.reportStatus({statusId: vm.status.statusId});
$ionicPopup.alert({
title: 'Report',
template: ' '
});
}
goToProfile.$inject = ['username'];
function goToProfile(username) {
var destinationState = $state.current.name.split('.')[0] + '.profile';
$state.go(destinationState, { username : username });
}
function getReportedStatuses(){
ReportService.getReportedStatuses(null, function(response){
vm.reportedStatuses = response;
}
);
$scope.$broadcast('scroll.refreshComplete');
}
function deleteStatus(statusId){
var confirmPopup = $ionicPopup.confirm({
title: 'Delete Status',
template: ' '
});
confirmPopup.then(deleted);
deleted.$inject = ['decision'];
function deleted(decision) {
if(decision) {
ReportService.deleteStatus({statusId: statusId}, function () {
ToastService.display('status.reportStatus.deleteToast');
}
);
$state.go($state.current, {}, {reload: true});
}
}
}
function approveStatus(statusId){
var confirmPopup = $ionicPopup.confirm({
title: 'Approve Status',
template: ' '
});
confirmPopup.then(approved);
approved.$inject = ['decision'];
function approved(decision) {
if(decision) {
ReportService.approveStatus({statusId: statusId}, function () {
ToastService.display('status.reportStatus.approveToast');
}
);
$state.go($state.current, {}, {reload: true});
}
}
}
remove.$inject = ['status'];
function remove(status) {
vm.statuses.splice(vm.statuses.indexOf(status), 1);
}
function hasReportedStatus() {
return vm.reportedStatuses.length > 0
}
}
})();
================================================
FILE: mobile/www/app/components/home/more/reportedStatus/reportedStatus.html
================================================
================================================
FILE: mobile/www/app/components/home/more/reportedStatus/reportedStatus.js
================================================
/**
* Created by emilyklein on 7/11/16.
*/
(function() {
'use strict';
angular.module('tatami')
.config(config);
config.$inject = ['$stateProvider'];
function config($stateProvider) {
$stateProvider
.state('reportedStatus', {
cache: false,
url: '/reportedStatus',
parent: 'more',
views: {
'more@home': {
templateUrl: 'app/components/home/more/reportedStatus/reportedStatus.html',
controller: 'ReportedStatusController',
controllerAs: 'vm'
}
}
});
}
angular.module('tatami')
.run(run);
run.$inject = ['TatamiState'];
function run(TatamiState) {
TatamiState.addProfileState('reportedStatus', 'home');
TatamiState.addConversationState('reportedStatus', 'home');
TatamiState.addTagState('reportedStatus', 'home');
}
})();
================================================
FILE: mobile/www/app/components/home/more/settings/settings.controller.js
================================================
(function() {
'use strict';
angular.module('tatami')
.controller('SettingsController', settingsController);
settingsController.$inject = ['$scope', '$translate', 'currentUser'];
function settingsController($scope, $translate, currentUser) {
var vm = this;
vm.currentUser = currentUser;
vm.language = window.localStorage.getItem('language');
vm.languages = [
{
langKey: 'en',
translateKey: 'more.language.english'
},
{
langKey: 'fr',
translateKey: 'more.language.french'
}
];
$scope.$watch('vm.language', updateLanguage);
function updateLanguage(language) {
$translate.use(language);
window.localStorage.setItem('language', language);
}
}
})();
================================================
FILE: mobile/www/app/components/home/more/settings/settings.html
================================================
{{ language.translateKey | translate }}
================================================
FILE: mobile/www/app/components/home/more/settings/settings.js
================================================
(function() {
'use strict';
angular.module('tatami')
.config(config);
config.$inject = ['$stateProvider'];
function config($stateProvider) {
$stateProvider
.state('settings', {
url: '/settings',
parent: 'more',
views: {
'more@home': {
templateUrl: 'app/components/home/more/settings/settings.html',
controller: 'SettingsController',
controllerAs: 'vm'
}
}
});
}
angular.module('tatami')
.run(run);
run.$inject = ['TatamiState'];
function run(TatamiState) {
TatamiState.addProfileState('settings', 'home');
TatamiState.addConversationState('settings', 'home');
TatamiState.addTagState('settings', 'home');
}
})();
================================================
FILE: mobile/www/app/components/home/timeline/timeline.controller.js
================================================
(function() {
'use strict';
angular.module('tatami')
.controller('TimelineCtrl', timelineCtrl);
timelineCtrl.$inject = ['statuses', 'currentUser', 'TatamiStatusRefresherService'];
function timelineCtrl(statuses, currentUser, TatamiStatusRefresherService) {
var vm = this;
vm.statuses = statuses;
vm.currentUser = currentUser;
vm.getNewStatuses = getNewStatuses;
vm.getOldStatuses = getOldStatuses;
function getNewStatuses() {
return TatamiStatusRefresherService.refreshHomeTimeline();
}
getOldStatuses.$inject = ['finalStatus'];
function getOldStatuses(finalStatus) {
return TatamiStatusRefresherService.getOldFromHomeTimeline(finalStatus);
}
}
})();
================================================
FILE: mobile/www/app/components/home/timeline/timeline.html
================================================
================================================
FILE: mobile/www/app/components/home/timeline/timeline.js
================================================
(function() {
'use strict';
angular.module('tatami')
.config(config);
config.$inject = ['$stateProvider'];
function config($stateProvider) {
$stateProvider
.state('timeline', {
url: '/timeline',
parent: 'home',
views: {
'timeline': {
templateUrl: 'app/components/home/timeline/timeline.html',
controller: 'TimelineCtrl',
controllerAs: 'vm'
}
},
resolve: {
statuses: getStatuses
}
});
getStatuses.$inject = ['StatusService'];
function getStatuses(StatusService) {
return StatusService.getHomeTimeline().$promise;
}
}
angular.module('tatami')
.run(run);
run.$inject = ['TatamiState'];
function run(TatamiState) {
TatamiState.addProfileState('timeline', 'home');
TatamiState.addConversationState('timeline', 'home');
TatamiState.addTagState('timeline', 'home');
}
})();
================================================
FILE: mobile/www/app/components/login/login.controller.js
================================================
(function () {
'use strict';
angular.module('tatami')
.controller('LoginCtrl', loginCtrl);
loginCtrl.$inject = [
'TatamiEndpoint',
'$scope',
'$state',
'$http',
'$localStorage',
'$ionicLoading',
'PathService',
'$ionicHistory',
'ToastService'
];
function loginCtrl(TatamiEndpoint, $scope, $state, $http, $localStorage, $ionicLoading, PathService, $ionicHistory, ToastService) {
var vm = this;
$scope.$on("$ionicView.enter", function () {
$ionicHistory.clearCache();
$ionicHistory.clearHistory();
var newEndpoint = TatamiEndpoint.getEndpoint();
if (vm.lastEndpoint.url !== newEndpoint.url) {
vm.lastEndpoint.url = newEndpoint.url;
vm.failed = false;
}
});
vm.user = {
remember: false
};
vm.failed = false;
vm.login = login;
vm.tryGoogleLogin = tryGoogleLogin;
vm.goToServerConfig = goToServerConfig;
vm.lastEndpoint = {url: ''};
function login() {
var data = "j_username=" + encodeURIComponent(vm.user.email) + "&j_password="
+ encodeURIComponent(vm.user.password);
return $http.post('/tatami/rest/authentication', data, {
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"Accept": "application/json"
}
}).success(function (data) {
$localStorage.set('token', data.token);
vm.user = {remember: false};
$state.go('timeline');
}).error(function () {
vm.failed = true;
});
}
function tryGoogleLogin() {
$http({
url: '/tatami/rest/client/id',
method: 'GET'
}).then(function (data) {
var clientId;
if (data && data.data && data.data.stringList) {
// Old tatami return the clientId in the stringList property
clientId = data.data.stringList[0];
} else if (data && data.data && data.data.clientId) {
clientId = data.data.clientId;
}
// Do Google login or display an error message
if (clientId) {
googleLogin(clientId);
} else {
ToastService.display('login.googleUnavaible');
}
}, function () {
ToastService.display('login.googleUnavaible');
});
}
function googleLogin(clientId) {
var emailScope = 'https://www.googleapis.com/auth/plus.profile.emails.read';
var profileScope = 'https://www.googleapis.com/auth/plus.me';
var googleUrl = 'https://accounts.google.com/o/oauth2/auth?' +
'client_id=' + clientId + '&' +
'redirect_uri=http://localhost/callback&' +
'scope=' + emailScope + ' ' + profileScope + '&' +
'approval_prompt=force&response_type=code&access_type=offline';
var ref = window.open(googleUrl, '_blank', 'location=no');
ref.addEventListener('loadstart', onStart);
onStart.$inject = ['event'];
function onStart(event) {
if (event.url.indexOf('http://localhost/callback') === 0) {
ref.close();
$ionicLoading.show({
template: 'Login in progress... ',
hideOnStateChange: true
});
var requestToken = event.url.split("code=")[1];
$http({
url: '/tatami/rest/oauth/token',
method: 'POST',
headers: {
'x-auth-code-header': requestToken
}
}).then(onSuccess, onFail);
}
}
onSuccess.$inject = ['result'];
function onSuccess(result) {
$localStorage.set('token', result.data.token);
$state.go('timeline');
}
onFail.$inject = ['failure'];
function onFail() {
vm.failed = true;
}
}
function goToServerConfig() {
$state.go('server');
}
}
})();
================================================
FILE: mobile/www/app/components/login/login.html
================================================
================================================
FILE: mobile/www/app/components/login/login.js
================================================
(function() {
'use strict';
angular.module('tatami')
.config(config);
config.$inject = ['$stateProvider'];
function config($stateProvider) {
$stateProvider
.state('login', {
url: '/login',
parent: 'tatami',
templateUrl: 'app/components/login/login.html',
controller: 'LoginCtrl',
controllerAs: 'vm',
resolve: {
translatePartialLoader: getTranslatePartialLoader
}
});
getTranslatePartialLoader.$inject = ['$translate', '$translatePartialLoader'];
function getTranslatePartialLoader($translate, $translatePartialLoader) {
$translatePartialLoader.addPart('login');
return $translate.refresh();
}
}
})();
================================================
FILE: mobile/www/app/components/login/server/server.controller.js
================================================
(function() {
'use strict';
angular.module('tatami')
.controller('ServerController', serverController);
serverController.$inject = [
'$state',
'$http',
'$ionicHistory',
'$translate',
'$ionicPopup',
'PathService',
'TatamiEndpoint'
];
function serverController($state, $http, $ionicHistory, $translate, $ionicPopup, PathService, TatamiEndpoint) {
var vm = this;
vm.endpoint = TatamiEndpoint.getEndpoint().url || TatamiEndpoint.getDefault().url;
vm.success = true;
vm.previous = vm.endpoint;
vm.updateEndpoint = updateEndpoint;
vm.useDefaultEndpoint = useDefaultEndpoint;
vm.isLastAttempt = isLastAttempt;
function updateEndpoint() {
TatamiEndpoint.setEndpoint(vm.endpoint);
$http({
url: '/tatami/rest/client/id',
method: 'GET'
}).then(success, error);
}
function success(result) {
vm.success = true;
vm.previous = vm.endpoint;
var alertPopup = $ionicPopup.alert({
title: $translate.instant('server.endpoint.authenticate.title'),
template: ' '
});
alertPopup.then($state.go('login'));
}
function error(result) {
vm.success = false;
vm.previous = vm.endpoint;
$ionicPopup.alert({
title: $translate.instant('server.endpoint.error.title'),
template: ' '
});
TatamiEndpoint.reset();
}
function isLastAttempt() {
return vm.previous === vm.endpoint;
}
function useDefaultEndpoint() {
vm.endpoint = TatamiEndpoint.getDefault().url;
updateEndpoint();
}
}
})();
================================================
FILE: mobile/www/app/components/login/server/server.html
================================================
Submit
================================================
FILE: mobile/www/app/components/login/server/server.js
================================================
(function() {
'use strict';
angular.module('tatami')
.config(config);
config.$inject = ['$stateProvider'];
function config($stateProvider) {
$stateProvider
.state('server', {
url: '/server',
parent: 'login',
views: {
'@tatami': {
templateUrl: 'app/components/login/server/server.html',
controller: 'ServerController',
controllerAs: 'vm'
}
},
resolve: {
translatePartialLoader: getTranslatePartialLoader
}
});
getTranslatePartialLoader.$inject = ['$translate', '$translatePartialLoader'];
function getTranslatePartialLoader($translate, $translatePartialLoader) {
$translatePartialLoader.addPart('server');
$translate.refresh();
}
}
})();
================================================
FILE: mobile/www/app/components/post/post.controller.js
================================================
(function() {
'use strict';
angular.module('tatami')
.controller('PostCtrl', postCtrl);
postCtrl.$inject = [
'StatusService',
'PathService',
'$ionicHistory',
'$state',
'$cordovaCamera',
'$q',
'$ionicLoading',
'$ionicPopup',
'repliedToStatus',
'$scope',
'$cordovaGeolocation',
'ToastService'
];
function postCtrl(StatusService, PathService, $ionicHistory, $state, $cordovaCamera, $q, $ionicLoading, $ionicPopup, repliedToStatus, $scope, $cordovaGeolocation, ToastService) {
var vm = this;
vm.charCount = 750;
vm.status = {
content: repliedToStatus ? '@' + repliedToStatus.username : '',
statusPrivate: repliedToStatus ? repliedToStatus.private : false,
replyTo: repliedToStatus ? repliedToStatus.statusId : '',
replyToUsername: repliedToStatus ? repliedToStatus.username : '',
attachmentIds: [],
geoLocalization: ""
};
vm.images = [];
vm.isPosting = false;
vm.remainingLength = vm.charCount;
vm.newLineCount = 0; //This field takes into consideration the '\n' character that counts for 2 chars in the database.
vm.pasteFlag = false;
vm.shareLocation = false;
vm.post = post;
vm.reset = reset;
vm.close = close;
vm.getPicture = getPicture;
vm.getPictureFromLibrary = getPictureFromLibrary;
vm.remove = remove;
vm.paste = paste;
vm.updateLocation = updateLocation;
vm.updatePrivate = updatePrivate;
function post() {
upload().then(createPost);
}
function createPost(attachmentIds) {
vm.status.attachmentIds = attachmentIds;
StatusService.save(vm.status, function() {
reset();
$ionicHistory.clearCache();
$state.go('timeline');
});
}
function reset() {
vm.status = {
content: '',
statusPrivate: false,
attachmentIds: [],
geoLocalization: ""
};
vm.images = [];
vm.isPosting = false;
vm.shareLocation = false;
}
function close() {
$ionicHistory.goBack();
reset();
}
function getPicture() {
var options = {
quality: 10,
correctOrientation : true
};
$cordovaCamera.getPicture(options).then(store);
}
function getPictureFromLibrary() {
var options = {
quality: 10,
sourceType: Camera.PictureSourceType.PHOTOLIBRARY,
correctOrientation: true
};
$cordovaCamera.getPicture(options).then(store);
}
function store(fileUri) {
vm.images.push(fileUri);
}
function upload() {
var promises = [];
vm.isPosting = true;
var options = new FileUploadOptions();
var fileTransfer = new FileTransfer();
angular.forEach(vm.images, function(image) {
var deferred = $q.defer();
options.fileKey = 'uploadFile';
options.fileName = image.substr(image.lastIndexOf('/') + 1);
$ionicLoading.show({
template: 'Post in progress... ',
hideOnStateChange: true
});
fileTransfer.upload(image, '/tatami/rest/fileupload', onSuccess, onFail, options);
promises.push(deferred.promise);
function onSuccess(result) {
var jsonResult = JSON.parse(result.response)[0];
deferred.resolve(jsonResult.attachmentId);
}
function onFail(failure) {
$ionicLoading.hide();
var popupError = $ionicPopup.alert({
title: 'Error',
template: ' '
});
popupError.then(goToTimeline);
function goToTimeline() {
reset();
$state.go('timeline');
}
deferred.resolve(failure);
}
});
return $q.all(promises);
}
function remove(index) {
vm.images.splice(index, 1);
}
function updateRemainingLength(statusContent) {
vm.remainingLength = vm.charCount - statusContent.length - vm.newLineCount;
}
function updateNewLineCount(statusContent) {
vm.newLineCount = (statusContent.match(/\n/g) || []).length;
}
$scope.$watch('vm.status.content', function (newValue) {
if (newValue) {
updateNewLineCount(newValue);
updateRemainingLength(newValue);
if(vm.remainingLength < 0){
if(vm.pasteFlag){
$ionicLoading.show({
template: ' ',
duration: 2500
});
vm.pasteFlag = false;
}
vm.status.content = newValue.slice(0, vm.charCount - vm.newLineCount);
updateNewLineCount(vm.status.content);
updateRemainingLength(vm.status.content);
}
} else {
vm.remainingLength = vm.charCount;
vm.newLineCount = 0;
}
});
function paste(){
vm.pasteFlag = true;
}
function updateLocation() {
vm.shareLocation = !vm.shareLocation;
if(vm.shareLocation){
var posOptions = {timeout: 5000, enableHighAccuracy: false};
$cordovaGeolocation
.getCurrentPosition(posOptions)
.then(function (position) {
vm.status.geoLocalization = position.coords.latitude + ", " + position.coords.longitude;
ToastService.display('post.location.share');
}, function() {
vm.shareLocation = !vm.shareLocation;
ToastService.display('post.location.fail');
});
} else {
vm.status.geoLocalization = "";
}
}
function updatePrivate(){
vm.status.statusPrivate = !vm.status.statusPrivate;
vm.status.statusPrivate ? ToastService.display('post.private.yes') : ToastService.display('post.private.no');
}
}
})();
================================================
FILE: mobile/www/app/components/post/post.html
================================================
================================================
FILE: mobile/www/app/components/post/post.js
================================================
(function() {
'use strict';
angular.module('tatami')
.config(postConfig);
postConfig.$inject = ['$stateProvider'];
function postConfig($stateProvider) {
$stateProvider
.state('post', {
url: '/post/:statusId',
parent: 'tatami',
templateUrl: 'app/components/post/post.html',
controller: 'PostCtrl',
controllerAs: 'vm',
resolve: {
repliedToStatus: getRepliedToStatus,
translatePartialProvider: getTranslatePartialLoader
}
})
}
getRepliedToStatus.$inject = ['StatusService', '$stateParams'];
function getRepliedToStatus(StatusService, $stateParams) {
if($stateParams.statusId) {
return StatusService.get({ statusId: $stateParams.statusId }).$promise;
}
}
getTranslatePartialLoader.$inject = ['$translate', '$translatePartialLoader'];
function getTranslatePartialLoader($translate, $translatePartialLoader) {
$translatePartialLoader.addPart('post');
return $translate.refresh();
}
})();
================================================
FILE: mobile/www/app/components/post/postbar.directive.js
================================================
(function() {
'use strict';
angular.module('tatami')
.directive('tatamiPostBarAttach', tatamiPostBar);
function tatamiPostBar() {
var directive = {
restrict: 'A',
link: link
};
return directive;
}
function link(scope, element, attrs) {
ionic.on('native.keyboardshow', onShow, window);
ionic.on('native.keyboardhide', onHide, window);
//deprecated
ionic.on('native.showkeyboard', onShow, window);
ionic.on('native.hidekeyboard', onHide, window);
var scrollCtrl;
function onShow(e) {
if (ionic.Platform.isAndroid() && !ionic.Platform.isFullScreen) {
return;
}
if(attrs['tatamiPostBarAttach'] === 'true') {
var subheaderOffset = 43;
}
//for testing
var keyboardHeight = e.keyboardHeight || e.detail.keyboardHeight;
element.css('bottom', (keyboardHeight + subheaderOffset) + "px");
scrollCtrl = element.controller('$ionicScroll');
if (scrollCtrl) {
scrollCtrl.scrollView.__container.style.bottom = subheaderOffset + keyboardHeight + keyboardAttachGetClientHeight(element[0]) + "px";
}
}
function onHide() {
if (ionic.Platform.isAndroid() && !ionic.Platform.isFullScreen) {
return;
}
element.css('bottom', '');
if (scrollCtrl) {
scrollCtrl.scrollView.__container.style.bottom = '';
}
}
scope.$on('$destroy', function() {
ionic.off('native.keyboardshow', onShow, window);
ionic.off('native.keyboardhide', onHide, window);
//deprecated
ionic.off('native.showkeyboard', onShow, window);
ionic.off('native.hidekeyboard', onHide, window);
});
function keyboardAttachGetClientHeight(element) {
return element.clientHeight;
}
}
})();
================================================
FILE: mobile/www/app/shared/config/marked.config.js
================================================
(function() {
'use strict';
angular.module('tatami')
.config(markedConfig);
markedConfig.$inject = ['markedProvider'];
function markedConfig(markedProvider) {
markedProvider.setOptions({
gfm: true,
pedantic: false,
sanitize: true,
highlight: null,
urls: {
youtube : function(text, url){
var cap;
if((cap = /(youtu\.be\/|youtube\.com\/(watch\?(.*&)?v=|(embed|v)\/))([^\?&"'>]+)/.exec(url))){
return 'VIDEO ';
}
},
vimeo : function(text, url){
var cap;
if((cap = /^.*(vimeo\.com\/)((channels\/[A-z]+\/)|(groups\/[A-z]+\/videos\/))?([0-9]+)/.exec(url))){
return '';
}
},
dailymotion : function(text, url){
var cap;
if((cap = /^.+dailymotion.com\/(video|hub)\/([^_]+)[^#]*(#video=([^_&]+))?/.exec(url))){
return '';
}
},
gist : function(text, url){
var cap;
if((cap = /^.+gist.github.com\/(([A-z0-9-]+)\/)?([0-9A-z]+)/.exec(url))){
$.ajax({
url: cap[0] + '.json',
dataType: 'jsonp',
success: function(response){
if(response.stylesheet && $('link[href="' + response.stylesheet + '"]').length === 0){
var l = document.createElement("link"),
head = document.getElementsByTagName("head")[0];
l.type = "text/css";
l.rel = "stylesheet";
l.href = response.stylesheet;
head.insertBefore(l, head.firstChild);
}
var $elements = $('.gist' + cap[3]);
$elements.html(response.div);
}
});
return '
';
}
}
}
});
}
})();
================================================
FILE: mobile/www/app/shared/config/marked.filter.js
================================================
(function() {
'use strict';
angular.module('tatami')
.filter('markdown', markdown);
markdown.$inject = ['$sce'];
function markdown($sce) {
return function(content) {
return content ? marked(content) : '';
};
}
})();
================================================
FILE: mobile/www/app/shared/config/tatami.marked.js
================================================
/**
* marked - a markdown parser
* Copyright (c) 2011-2013, Christopher Jeffrey. (MIT Licensed)
* https://github.com/chjj/marked
*/
;(function() {
/**
* Block-Level Grammar
*/
var block = {
newline: /^\n+/,
code: /^( {4}[^\n]+\n*)+/,
fences: noop,
hr: /^( *[-*_]){3,} *(?:\n+|$)/,
heading: /^ *(#{1,6} ) *([^\n]+?) *#* *(?:\n+|$)/,
nptable: noop,
lheading: /^([^\n]+)\n *(=|-){3,} *\n*/,
blockquote: /^( *>[^\n]+(\n[^\n]+)*\n*)+/,
list: /^( *)(bull) [\s\S]+?(?:hr|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,
html: /^ *(?:comment|closed|closing) *(?:\n{2,}|\s*$)/,
def: /^ *\[([^\]]+)\]: *([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,
table: noop,
paragraph: /^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/,
text: /^[^\n]+/
};
block.bullet = /(?:[*+-]|\d+\.)/;
block.item = /^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/;
block.item = replace(block.item, 'gm')
(/bull/g, block.bullet)
();
block.list = replace(block.list)
(/bull/g, block.bullet)
('hr', /\n+(?=(?: *[-*_]){3,} *(?:\n+|$))/)
();
block._tag = '(?!(?:'
+ 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code'
+ '|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo'
+ '|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|@)\\b';
block.html = replace(block.html)
('comment', //)
('closed', /<(tag)[\s\S]+?<\/\1>/)
('closing', /])*?>/)
(/tag/g, block._tag)
();
block.paragraph = replace(block.paragraph)
('hr', block.hr)
('heading', block.heading)
('lheading', block.lheading)
('blockquote', block.blockquote)
('tag', '<' + block._tag)
('def', block.def)
();
/**
* Normal Block Grammar
*/
block.normal = merge({}, block);
/**
* GFM Block Grammar
*/
block.gfm = merge({}, block.normal, {
fences: /^ *(`{3,}|~{3,}) *(\w+)? *\n([\s\S]+?)\s*\1 *(?:\n+|$)/,
paragraph: /^/
});
block.gfm.paragraph = replace(block.paragraph)
('(?!', '(?!' + block.gfm.fences.source.replace('\\1', '\\2') + '|')
();
/**
* GFM + Tables Block Grammar
*/
block.tables = merge({}, block.gfm, {
nptable: /^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,
table: /^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/
});
/**
* Block Lexer
*/
function Lexer(options) {
this.tokens = [];
this.tokens.links = {};
this.options = options || marked.defaults;
this.rules = block.normal;
if (this.options.gfm) {
if (this.options.tables) {
this.rules = block.tables;
} else {
this.rules = block.gfm;
}
}
}
/**
* Expose Block Rules
*/
Lexer.rules = block;
/**
* Static Lex Method
*/
Lexer.lex = function(src, options) {
var lexer = new Lexer(options);
return lexer.lex(src);
};
/**
* Preprocessing
*/
Lexer.prototype.lex = function(src) {
src = src
.replace(/\r\n|\r/g, '\n')
.replace(/\t/g, ' ')
.replace(/\u00a0/g, ' ')
.replace(/\u2424/g, '\n');
return this.token(src, true);
};
/**
* Lexing
*/
Lexer.prototype.token = function(src, top) {
var src = src.replace(/^ +$/gm, '')
, next
, loose
, cap
, bull
, b
, item
, space
, i
, l;
while (src) {
// newline
if (cap = this.rules.newline.exec(src)) {
src = src.substring(cap[0].length);
if (cap[0].length > 1) {
this.tokens.push({
type: 'space'
});
}
}
// code
if (cap = this.rules.code.exec(src)) {
src = src.substring(cap[0].length);
cap = cap[0].replace(/^ {4}/gm, '');
this.tokens.push({
type: 'code',
text: !this.options.pedantic
? cap.replace(/\n+$/, '')
: cap
});
continue;
}
// fences (gfm)
if (cap = this.rules.fences.exec(src)) {
src = src.substring(cap[0].length);
this.tokens.push({
type: 'code',
lang: cap[2],
text: cap[3]
});
continue;
}
// heading
if (cap = this.rules.heading.exec(src)) {
src = src.substring(cap[0].length);
this.tokens.push({
type: 'heading',
depth: cap[1].length,
text: cap[2]
});
continue;
}
// table no leading pipe (gfm)
if (top && (cap = this.rules.nptable.exec(src))) {
src = src.substring(cap[0].length);
item = {
type: 'table',
header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */),
align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
cells: cap[3].replace(/\n$/, '').split('\n')
};
for (i = 0; i < item.align.length; i++) {
if (/^ *-+: *$/.test(item.align[i])) {
item.align[i] = 'right';
} else if (/^ *:-+: *$/.test(item.align[i])) {
item.align[i] = 'center';
} else if (/^ *:-+ *$/.test(item.align[i])) {
item.align[i] = 'left';
} else {
item.align[i] = null;
}
}
for (i = 0; i < item.cells.length; i++) {
item.cells[i] = item.cells[i].split(/ *\| */);
}
this.tokens.push(item);
continue;
}
// lheading
if (cap = this.rules.lheading.exec(src)) {
src = src.substring(cap[0].length);
this.tokens.push({
type: 'heading',
depth: cap[2] === '=' ? 1 : 2,
text: cap[1]
});
continue;
}
// hr
if (cap = this.rules.hr.exec(src)) {
src = src.substring(cap[0].length);
this.tokens.push({
type: 'hr'
});
continue;
}
// blockquote
if (cap = this.rules.blockquote.exec(src)) {
src = src.substring(cap[0].length);
this.tokens.push({
type: 'blockquote_start'
});
cap = cap[0].replace(/^ *> ?/gm, '');
// Pass `top` to keep the current
// "toplevel" state. This is exactly
// how markdown.pl works.
this.token(cap, top);
this.tokens.push({
type: 'blockquote_end'
});
continue;
}
// list
if (cap = this.rules.list.exec(src)) {
src = src.substring(cap[0].length);
this.tokens.push({
type: 'list_start',
ordered: isFinite(cap[2])
});
// Get each top-level item.
cap = cap[0].match(this.rules.item);
// Get bullet.
if (this.options.smartLists) {
bull = block.bullet.exec(cap[0])[0];
}
next = false;
l = cap.length;
i = 0;
for (; i < l; i++) {
item = cap[i];
// Remove the list item's bullet
// so it is seen as the next token.
space = item.length;
item = item.replace(/^ *([*+-]|\d+\.) +/, '');
// Outdent whatever the
// list item contains. Hacky.
if (~item.indexOf('\n ')) {
space -= item.length;
item = !this.options.pedantic
? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '')
: item.replace(/^ {1,4}/gm, '');
}
// Determine whether the next list item belongs here.
// Backpedal if it does not belong in this list.
if (this.options.smartLists && i !== l - 1) {
b = block.bullet.exec(cap[i+1])[0];
if (bull !== b && !(bull[1] === '.' && b[1] === '.')) {
src = cap.slice(i + 1).join('\n') + src;
i = l - 1;
}
}
// Determine whether item is loose or not.
// Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/
// for discount behavior.
loose = next || /\n\n(?!\s*$)/.test(item);
if (i !== l - 1) {
next = item[item.length-1] === '\n';
if (!loose) loose = next;
}
this.tokens.push({
type: loose
? 'loose_item_start'
: 'list_item_start'
});
// Recurse.
this.token(item, false);
this.tokens.push({
type: 'list_item_end'
});
}
this.tokens.push({
type: 'list_end'
});
continue;
}
// html
if (cap = this.rules.html.exec(src)) {
src = src.substring(cap[0].length);
this.tokens.push({
type: this.options.sanitize
? 'paragraph'
: 'html',
pre: cap[1] === 'pre',
text: cap[0]
});
continue;
}
// def
if (top && (cap = this.rules.def.exec(src))) {
src = src.substring(cap[0].length);
this.tokens.links[cap[1].toLowerCase()] = {
href: cap[2],
title: cap[3]
};
continue;
}
// table (gfm)
if (top && (cap = this.rules.table.exec(src))) {
src = src.substring(cap[0].length);
item = {
type: 'table',
header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */),
align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
cells: cap[3].replace(/(?: *\| *)?\n$/, '').split('\n')
};
for (i = 0; i < item.align.length; i++) {
if (/^ *-+: *$/.test(item.align[i])) {
item.align[i] = 'right';
} else if (/^ *:-+: *$/.test(item.align[i])) {
item.align[i] = 'center';
} else if (/^ *:-+ *$/.test(item.align[i])) {
item.align[i] = 'left';
} else {
item.align[i] = null;
}
}
for (i = 0; i < item.cells.length; i++) {
item.cells[i] = item.cells[i]
.replace(/^ *\| *| *\| *$/g, '')
.split(/ *\| */);
}
this.tokens.push(item);
continue;
}
// top-level paragraph
if (top && (cap = this.rules.paragraph.exec(src))) {
src = src.substring(cap[0].length);
this.tokens.push({
type: 'paragraph',
text: cap[1][cap[1].length-1] === '\n'
? cap[1].slice(0, -1)
: cap[1]
});
continue;
}
// text
if (cap = this.rules.text.exec(src)) {
// Top-level should never reach here.
src = src.substring(cap[0].length);
this.tokens.push({
type: 'text',
text: cap[0]
});
continue;
}
if (src) {
throw new
Error('Infinite loop on byte: ' + src.charCodeAt(0));
}
}
return this.tokens;
};
/**
* Inline-Level Grammar
*/
var inline = {
escape: /^\\([\\`*{}\[\]()#+\-.!_>])/,
autolink: /^<([^ >]+(@|:\/)[^ >]+)>/,
url: noop,
tag: /^|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/,
link: /^!?\[(inside)\]\(href\)/,
reflink: /^!?\[(inside)\]\s*\[([^\]]*)\]/,
nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,
strong: /^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/,
em: /^\b_((?:__|[\s\S])+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/,
code: /^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/,
br: /^ {2,}\n(?!\s*$)/,
mail: /^([^\s !"#$%'()*+,.\/:;<=>?@\\\[\]\^_`{|}~-]+(@|:\/)[^\s !"#$%'()*+,.\/:;<=>?@\\\[\]\^_`{|}~-]+.[^\s !"#$%'()*+,.\/:;<=>?@\\\[\]\^_`{|}~-]+)/,
mention: /^@([A-Za-z0-9!#$%&'*+\/=?\^_`{|}~\-]+(?:\.[A-Za-z0-9!#$%&'*+\/=?\^_`{|}~\-]+)*(?!\*))/,
tags: /^#([^\s!"#$%'()*+,.\/:;<=>?@\\\[\]\^_`{|}~-]+(?:\.[\^!"#$%'()*+,.\/:;<=>?@\\\[\]\^_`{|}~-]+)*)(?!\*)/,
del: noop,
text: /^[\s\S]+?(?=[\\?(?:\s+['"]([\s\S]*?)['"])?\s*/;
inline.link = replace(inline.link)
('inside', inline._inside)
('href', inline._href)
();
inline.reflink = replace(inline.reflink)
('inside', inline._inside)
();
/**
* Normal Inline Grammar
*/
inline.normal = merge({}, inline);
/**
* Pedantic Inline Grammar
*/
inline.pedantic = merge({}, inline.normal, {
strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,
em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/
});
/**
* GFM Inline Grammar
*/
inline.gfm = merge({}, inline.normal, {
escape: replace(inline.escape)('])', '~|])')(),
url: /^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/,
del: /^~~(?=\S)([\s\S]*?\S)~~/,
text: replace(inline.text)
(']|', '#@~]|')
('|', '|https?://|')
('|', '||')
()
});
/**
* GFM + Line Breaks Inline Grammar
*/
inline.breaks = merge({}, inline.gfm, {
br: replace(inline.br)('{2,}', '*')(),
text: replace(inline.gfm.text)('{2,}', '*')()
});
/**
* Inline Lexer & Compiler
*/
function InlineLexer(links, options) {
this.options = options || marked.defaults;
this.links = links;
this.rules = inline.normal;
if (!this.links) {
throw new
Error('Tokens array requires a `links` property.');
}
if (this.options.gfm) {
if (this.options.breaks) {
this.rules = inline.breaks;
} else {
this.rules = inline.gfm;
}
} else if (this.options.pedantic) {
this.rules = inline.pedantic;
}
}
/**
* Expose Inline Rules
*/
InlineLexer.rules = inline;
/**
* Static Lexing/Compiling Method
*/
InlineLexer.output = function(src, links, options) {
var inline = new InlineLexer(links, options);
return inline.output(src);
};
/**
* Lexing/Compiling
*/
InlineLexer.prototype.output = function(src) {
var out = ''
, link
, text
, href
, cap;
while (src) {
// escape
if (cap = this.rules.escape.exec(src)) {
src = src.substring(cap[0].length);
out += cap[1];
continue;
}
// autolink
if (cap = this.rules.autolink.exec(src)) {
src = src.substring(cap[0].length);
if (cap[2] === '@') {
text = cap[1][6] === ':'
? this.mangle(cap[1].substring(7))
: this.mangle(cap[1]);
href = this.mangle('mailto:') + text;
} else {
href = escape(cap[1]);
text = shortenUrl(href);
}
out += ''
+ text
+ ' ';
continue;
}
// autolink
if (cap = this.rules.mail.exec(src)) {
src = src.substring(cap[0].length);
if (cap[2] === '@') {
text = cap[1][6] === ':'
? this.mangle(cap[1].substring(7))
: this.mangle(cap[1]);
href = this.mangle('mailto:') + text;
} else {
href = escape(cap[1]);
text = shortenUrl(href);
}
out += ''
+ text
+ ' ';
continue;
}
// url (gfm)
if (cap = this.rules.url.exec(src)) {
var html;
src = src.substring(cap[0].length);
href = escape(cap[1]);
text = shortenUrl(href);
for(var key in this.options.urls){
html = this.options.urls[key](text, href);
if(html){
out += html;
break;
}
}
if(!html)
out += ''
+ text
+ ' ';
continue;
}
// tag
if (cap = this.rules.tag.exec(src)) {
src = src.substring(cap[0].length);
out += this.options.sanitize
? escape(cap[0])
: cap[0];
continue;
}
// link
if (cap = this.rules.link.exec(src)) {
src = src.substring(cap[0].length);
out += this.outputLink(cap, {
href: cap[2],
title: cap[3]
});
continue;
}
// reflink, nolink
if ((cap = this.rules.reflink.exec(src))
|| (cap = this.rules.nolink.exec(src))) {
src = src.substring(cap[0].length);
link = (cap[2] || cap[1]).replace(/\s+/g, ' ');
link = this.links[link.toLowerCase()];
if (!link || !link.href) {
out += cap[0][0];
src = cap[0].substring(1) + src;
continue;
}
out += this.outputLink(cap, link);
continue;
}
// strong
if (cap = this.rules.strong.exec(src)) {
src = src.substring(cap[0].length);
out += ''
+ this.output(cap[2] || cap[1])
+ ' ';
continue;
}
// em
if (cap = this.rules.em.exec(src)) {
src = src.substring(cap[0].length);
out += ''
+ this.output(cap[2] || cap[1])
+ ' ';
continue;
}
// code
if (cap = this.rules.code.exec(src)) {
src = src.substring(cap[0].length);
out += ''
+ escape(cap[2], true)
+ '';
continue;
}
// mention
if (cap = inline.mention.exec(src)) {
src = src.substring(cap[0].length);
out += ''
+ cap[0]
+ ' ';
continue;
}
// tags
if (cap = inline.tags.exec(src)) {
src = src.substring(cap[0].length);
out += '' + cap[0] + ' ';
continue;
}
// br
if (cap = this.rules.br.exec(src)) {
src = src.substring(cap[0].length);
out += ' ';
continue;
}
// del (gfm)
if (cap = this.rules.del.exec(src)) {
src = src.substring(cap[0].length);
out += ''
+ this.output(cap[1])
+ '';
continue;
}
// text
if (cap = this.rules.text.exec(src)) {
src = src.substring(cap[0].length);
out += escape(cap[0]);
continue;
}
if (src) {
throw new
Error('Infinite loop on byte: ' + src.charCodeAt(0));
}
}
return out;
};
/**
* Compile Link
*/
InlineLexer.prototype.outputLink = function(cap, link) {
if (cap[0][0] !== '!') {
return ''
+ this.output(cap[1])
+ ' ';
} else {
return ' ';
}
};
/**
* Mangle Links
*/
InlineLexer.prototype.mangle = function(text) {
var out = ''
, l = text.length
, i = 0
, ch;
for (; i < l; i++) {
ch = text.charCodeAt(i);
if (Math.random() > 0.5) {
ch = 'x' + ch.toString(16);
}
out += '' + ch + ';';
}
return out;
};
/**
* Parsing & Compiling
*/
function Parser(options) {
this.tokens = [];
this.token = null;
this.options = options || marked.defaults;
}
/**
* Static Parse Method
*/
Parser.parse = function(src, options) {
var parser = new Parser(options);
return parser.parse(src);
};
/**
* Parse Loop
*/
Parser.prototype.parse = function(src) {
this.inline = new InlineLexer(src.links, this.options);
this.tokens = src.reverse();
var out = '';
while (this.next()) {
out += this.tok();
}
return out;
};
/**
* Next Token
*/
Parser.prototype.next = function() {
return this.token = this.tokens.pop();
};
/**
* Preview Next Token
*/
Parser.prototype.peek = function() {
return this.tokens[this.tokens.length-1] || 0;
};
/**
* Parse Text Tokens
*/
Parser.prototype.parseText = function() {
var body = this.token.text;
while (this.peek().type === 'text') {
body += '\n' + this.next().text;
}
return this.inline.output(body);
};
/**
* Parse Current Token
*/
Parser.prototype.tok = function() {
switch (this.token.type) {
case 'space': {
return '';
}
case 'hr': {
return ' \n';
}
case 'heading': {
return ''
+ this.inline.output(this.token.text)
+ ' \n';
}
case 'code': {
if (this.options.highlight) {
var code = this.options.highlight(this.token.text, this.token.lang);
if (code != null && code !== this.token.text) {
this.token.escaped = true;
this.token.text = code;
}
}
if (!this.token.escaped) {
this.token.text = escape(this.token.text, true);
}
return ''
+ this.token.text
+ ' \n';
}
case 'table': {
var body = ''
, heading
, i
, row
, cell
, j;
// header
body += '\n\n';
for (i = 0; i < this.token.header.length; i++) {
heading = this.inline.output(this.token.header[i]);
body += this.token.align[i]
? '' + heading + ' \n'
: '' + heading + ' \n';
}
body += ' \n \n';
// body
body += '\n'
for (i = 0; i < this.token.cells.length; i++) {
row = this.token.cells[i];
body += '\n';
for (j = 0; j < row.length; j++) {
cell = this.inline.output(row[j]);
body += this.token.align[j]
? '' + cell + ' \n'
: '' + cell + ' \n';
}
body += ' \n';
}
body += ' \n';
return '\n';
}
case 'blockquote_start': {
var body = '';
while (this.next().type !== 'blockquote_end') {
body += this.tok();
}
return '\n'
+ body
+ ' \n';
}
case 'list_start': {
var type = this.token.ordered ? 'ol' : 'ul'
, body = '';
while (this.next().type !== 'list_end') {
body += this.tok();
}
return '<'
+ type
+ '>\n'
+ body
+ ''
+ type
+ '>\n';
}
case 'list_item_start': {
var body = '';
while (this.next().type !== 'list_item_end') {
body += this.token.type === 'text'
? this.parseText()
: this.tok();
}
return ''
+ body
+ ' \n';
}
case 'loose_item_start': {
var body = '';
while (this.next().type !== 'list_item_end') {
body += this.tok();
}
return ''
+ body
+ ' \n';
}
case 'html': {
return !this.token.pre && !this.options.pedantic
? this.inline.output(this.token.text)
: this.token.text;
}
case 'paragraph': {
return ''
+ this.inline.output(this.token.text)
+ '
\n';
}
case 'text': {
return ''
+ this.parseText()
+ '
\n';
}
}
};
/**
* Helpers
*/
function escape(html, encode) {
return html
.replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
function shortenUrl(url) {
if (url.length < 67) {
return url;
} else {
return url.substring(0, 64) + "...";
}
}
function replace(regex, opt) {
regex = regex.source;
opt = opt || '';
return function self(name, val) {
if (!name) return new RegExp(regex, opt);
val = val.source || val;
val = val.replace(/(^|[^\[])\^/g, '$1');
regex = regex.replace(name, val);
return self;
};
}
function noop() {}
noop.exec = noop;
function merge(obj) {
var i = 1
, target
, key;
for (; i < arguments.length; i++) {
target = arguments[i];
for (key in target) {
if (Object.prototype.hasOwnProperty.call(target, key)) {
obj[key] = target[key];
}
}
}
return obj;
}
/**
* Marked
*/
function marked(src, opt) {
try {
if (opt) opt = merge({}, marked.defaults, opt);
return Parser.parse(Lexer.lex(src, opt), opt);
} catch (e) {
e.message += '\nPlease report this to https://github.com/chjj/marked.';
if ((opt || marked.defaults).silent) {
return 'An error occured:
'
+ escape(e.message + '', true)
+ ' ';
}
throw e;
}
}
/**
* Options
*/
marked.options =
marked.setOptions = function(opt) {
merge(marked.defaults, opt);
return marked;
};
marked.defaults = {
gfm: true,
tables: true,
breaks: false,
pedantic: false,
sanitize: false,
// urls : [function(text, url){ return 'html'; }]
smartLists: false,
silent: false,
highlight: null,
langPrefix: 'lang-'
};
/**
* Expose
*/
marked.Parser = Parser;
marked.parser = Parser.parse;
marked.Lexer = Lexer;
marked.lexer = Lexer.lex;
marked.InlineLexer = InlineLexer;
marked.inlineLexer = InlineLexer.output;
marked.parse = marked;
if (typeof exports === 'object') {
module.exports = marked;
} else if (typeof define === 'function' && define.amd) {
define(function() { return marked; });
} else {
this.marked = marked;
}
}).call(function() {
return this || (typeof window !== 'undefined' ? window : global);
}());
================================================
FILE: mobile/www/app/shared/interceptor/auth.interceptor.js
================================================
(function() {
'use strict';
angular.module('tatami')
.factory('authInterceptor', authInterceptor)
.factory('authExpiredInterceptor', authExpiredInterceptor)
.factory('endpointInterceptor', endpointInterceptor);
authInterceptor.$inject = ['$rootScope', '$q', '$location', '$localStorage'];
function authInterceptor($rootScope, $q, $location, $localStorage) {
var interceptor = {
request: request
};
return interceptor;
request.$inject = ['config'];
function request(config) {
config.headers = config.headers || {};
var token = $localStorage.get('token');
if (token && token.expires && token.expires > new Date().getTime()) {
config.headers['x-auth-token'] = token.token;
}
return config;
}
}
authExpiredInterceptor.$inject = ['$q', '$localStorage', '$injector'];
function authExpiredInterceptor($q, $localStorage, $injector) {
var interceptor = {
responseError: responseError
};
return interceptor;
responseError.$inject = ['response'];
function responseError(response) {
if(response.status === 401 && (response.data.error == 'invalid_token' || response.data.error == 'Unauthorized')) {
$localStorage.signOut();
var $state = $injector.get('$state');
$state.go('login');
}
return $q.reject(response);
}
}
/* endpointInterceptor built to replace PathService.buildPath(resource)
*
* Old implementation was causing issues - for some reason, REST path requests randomly started getting cached
* so an attempt to access /rest/authentication wouldn't reach buildPath but would return the endpoint used initially
* This interceptor never caches, so it's a better implementation.
*
* NOTE: Only make requests to server beginning with "/"; only access documents locally using the first folder and
* NOT "/"; static url requests (i.e. to google.com) should begin with http://, NEVER "/".
*
* ONLY USE "/" AS FIRST CHARACTER OF REQUESTS IF YOU NEED THE ENDPOINT URL TO BE A PREFIX
*/
endpointInterceptor.$inject = ['$localStorage'];
function endpointInterceptor($localStorage) {
var interceptor = {
request: request
};
return interceptor;
request.$inject = ['config'];
function request(config) {
if(config.url.indexOf("/") == 0) {
config.url = $localStorage.get('endpoint').url + config.url;
}
return config;
}
}
})();
================================================
FILE: mobile/www/app/shared/providers/provider.js
================================================
(function() {
'use strict';
angular.module('tatami.providers', []);
})();
================================================
FILE: mobile/www/app/shared/providers/tatami.state.provider.js
================================================
(function() {
'use strict';
// This will dynamically create any tab substates inside the current tab. If in the timeline tab, we will
// create a timeline.status state
angular.module('tatami.providers')
.provider('TatamiState', tatamiState);
tatamiState.$inject = ['$stateProvider'];
function tatamiState($stateProvider) {
this.$get = tatamiStateHelper;
function tatamiStateHelper() {
var profileViewConfig = {
templateUrl: 'app/shared/state/profile/profile.html',
controller: 'ProfileCtrl',
controllerAs: 'vm'
};
var profileViews = [];
profileViews['suggested@follow'] = { 'suggested@follow': profileViewConfig };
profileViews['following@follow'] = { 'following@follow': profileViewConfig };
profileViews['follower@follow'] = { 'follower@follow': profileViewConfig };
profileViews['timeline@home'] = { 'timeline@home': profileViewConfig };
profileViews['mentions@home'] = { 'mentions@home': profileViewConfig };
profileViews['favorites@home'] = { 'favorites@home': profileViewConfig };
profileViews['more@home'] = { 'more@home': profileViewConfig };
profileViews['company@home'] = { 'more@home': profileViewConfig };
profileViews['blockedusers@home'] = { 'more@home': profileViewConfig };
profileViews['allusers@home'] = { 'more@home': profileViewConfig };
var conversationViewConfig = {
templateUrl: 'app/shared/state/conversation/conversation.html',
controller: 'ConversationCtrl',
controllerAs: 'vm'
};
var conversationViews = [];
conversationViews['suggested@follow'] = { 'suggested@follow': conversationViewConfig };
conversationViews['following@follow'] = { 'following@follow': conversationViewConfig };
conversationViews['follower@follow'] = { 'follower@follow': conversationViewConfig };
conversationViews['timeline@home'] = { 'timeline@home': conversationViewConfig };
conversationViews['mentions@home'] = { 'mentions@home': conversationViewConfig };
conversationViews['favorites@home'] = { 'favorites@home': conversationViewConfig };
conversationViews['company@home'] = { 'more@home': conversationViewConfig };
conversationViews['blockedusers@home'] = { 'more@home': profileViewConfig };
conversationViews['allusers@home'] = { 'more@home': profileViewConfig };
var tagViewConfig = {
templateUrl: 'app/shared/state/tag/tag.html',
controller: 'TagCtrl',
controllerAs: 'vm'
};
var tagViews = [];
tagViews['suggested@follow'] = { 'suggested@follow': tagViewConfig };
tagViews['following@follow'] = { 'following@follow': tagViewConfig };
tagViews['follower@follow'] = { 'follower@follow': tagViewConfig };
tagViews['timeline@home'] = { 'timeline@home': tagViewConfig };
tagViews['mentions@home'] = { 'mentions@home': tagViewConfig };
tagViews['favorites@home'] = { 'favorites@home': tagViewConfig };
tagViews['company@home'] = { 'more@home': tagViewConfig };
tagViews['blockedusers@home'] = { 'more@home': profileViewConfig };
tagViews['allusers@home'] = { 'more@home': profileViewConfig };
var service = {
addProfileState: addProfileState,
addConversationState: addConversationState,
addTagState: addTagState
};
addProfileState.$inject = ['prefixName', 'parentName'];
function addProfileState(prefixName, parentName) {
$stateProvider.state(prefixName + '.profile', {
url: '/profile/:username',
views: profileViews[prefixName + '@' + parentName],
resolve: {
user: getUser,
statuses: getStatuses,
currentUser: getCurrentUser
}
});
getUser.$inject = ['UserService', '$stateParams'];
function getUser(UserService, $stateParams) {
return UserService.get({ username : $stateParams.username }).$promise;
}
getStatuses.$inject = ['user', 'StatusService'];
function getStatuses(user, StatusService) {
return StatusService.getUserTimeline({ username: user.username }).$promise;
}
getCurrentUser.$inject = ['currentUser'];
function getCurrentUser(currentUser) {
return currentUser;
}
}
addConversationState.$inject = ['prefixName', 'parentName'];
function addConversationState(prefixName, parentName) {
$stateProvider.state(prefixName + '.conversation', {
url: '/conversation/:statusId',
views: conversationViews[prefixName + '@' + parentName],
resolve: {
originalStatus: getOriginalStatus,
conversation: getConversation
}
});
getOriginalStatus.$inject = ['StatusService', '$stateParams'];
function getOriginalStatus(StatusService, $stateParams) {
return StatusService.get({ statusId : $stateParams.statusId }).$promise;
}
getConversation.$inject = ['StatusService', '$stateParams'];
function getConversation(StatusService, $stateParams) {
return StatusService.getDetails({ statusId : $stateParams.statusId }).$promise;
}
}
addTagState.$inject = ['prefixName', 'parentName'];
function addTagState(prefixName, parentName) {
$stateProvider.state(prefixName + '.tag', {
url: '/tag/:tag',
views: tagViews[prefixName + '@' + parentName],
resolve: {
tag: getTag,
statuses: getStatuses
}
});
getTag.$inject = ['$stateParams'];
function getTag($stateParams) {
return $stateParams.tag;
}
getStatuses.$inject = ['TagService', '$stateParams'];
function getStatuses(TagService, $stateParams) {
return TagService.getTagTimeline({ tag: $stateParams.tag }).$promise;
}
}
return service;
}
}
})();
================================================
FILE: mobile/www/app/shared/services/HomeService.js
================================================
(function() {
'use strict';
angular.module('tatami.services')
.factory('HomeService', homeService);
homeService.$inject = ['$resource', 'PathService'];
function homeService($resource, PathService) {
return $resource(null, null,
{
'getMentions': {
method: 'GET', isArray: true, url: '/tatami/rest/mentions',
transformResponse: responseTransform
},
'getFavorites': {
method: 'GET', isArray: true, url: '/tatami/rest/favorites',
transformResponse: responseTransform
},
'getCompanyTimeline': {
method: 'GET', isArray: true, url: '/tatami/rest/company',
transformResponse: responseTransform
}
});
responseTransform.$inject = ['statuses'];
function responseTransform(statuses) {
statuses = angular.fromJson(statuses);
for(var i = 0; i < statuses.length; i++) {
statuses[i]['avatarURL'] = PathService.getAvatar(statuses[i]);
if(statuses[i].geoLocalization) {
var latitude = statuses[i].geoLocalization.split(',')[0].trim();
var longitude = statuses[i].geoLocalization.split(',')[1].trim();
statuses[i]['locationURL'] =
'https://www.openstreetmap.org/?mlon='
+ longitude + '&mlat=' + latitude;
}
}
return statuses;
}
}
})();
================================================
FILE: mobile/www/app/shared/services/ProfileService.js
================================================
(function() {
'use strict';
angular.module('tatami.services')
.factory('ProfileService', profileService);
profileService.$inject = ['$resource', 'PathService'];
function profileService($resource, PathService) {
return $resource('/tatami/rest/account/profile', null,
{
'get': {
method: 'GET',
transformResponse: function (profile) {
profile = angular.fromJson(profile);
profile['avatarURL'] = PathService.getAvatar(profile);
return profile;
}
},
'update': {
method: 'PUT',
transformRequest: function (profile) {
delete profile['avatarURL'];
return angular.toJson(profile);
}
}
});
}
})();
================================================
FILE: mobile/www/app/shared/services/StatusService.js
================================================
(function() {
'use strict';
angular.module('tatami.services')
.factory('StatusService', statusService);
statusService.$inject = ['$resource', 'PathService'];
function statusService($resource, PathService) {
var responseTransform = function (statuses) {
statuses = angular.fromJson(statuses);
for (var i = 0; i < statuses.length; i++) {
statuses[i]['avatarURL'] = PathService.getAvatar(statuses[i]);
if (statuses[i].geoLocalization) {
var latitude = statuses[i].geoLocalization.split(',')[0].trim();
var longitude = statuses[i].geoLocalization.split(',')[1].trim();
statuses[i]['locationURL'] =
'https://www.openstreetmap.org/?mlon='
+ longitude + '&mlat=' + latitude;
}
}
return statuses;
};
return $resource('/tatami/rest/statuses/:statusId', null,
{
'get': {
method: 'GET',
cache: false,
transformResponse: function (status) {
status = angular.fromJson(status);
status.avatarURL = PathService.getAvatar(status);
if (status.geoLocalization) {
var latitude = status.geoLocalization.split(',')[0].trim();
var longitude = status.geoLocalization.split(',')[1].trim();
status['locationURL'] =
'https://www.openstreetmap.org/?mlon='
+ longitude + '&mlat=' + latitude;
}
return status;
}
},
'getHomeTimeline': {
method: 'GET',
isArray: true,
url: '/tatami/rest/statuses/home_timeline',
cache: false,
transformResponse: responseTransform
},
'getUserTimeline': {
method: 'GET',
isArray: true,
params: {username: '@username'},
url: '/tatami/rest/statuses/:username/timeline',
cache: false,
transformResponse: responseTransform
},
'getDetails': {
method: 'GET',
params: {statusId: '@statusId'},
url: '/tatami/rest/statuses/details/:statusId',
cache: false,
transformResponse: function (details) {
details = angular.fromJson(details);
for (var i = 0; i < details.discussionStatuses.length; i++) {
details.discussionStatuses[i]['avatarURL'] = PathService.getAvatar(details.discussionStatuses[i]);
if (details.discussionStatuses[i].geoLocalization) {
var latitude = details.discussionStatuses[i].geoLocalization.split(',')[0].trim();
var longitude = details.discussionStatuses[i].geoLocalization.split(',')[1].trim();
details.discussionStatuses[i]['locationURL'] =
'https://www.openstreetmap.org/?mlon='
+ longitude + '&mlat=' + latitude;
}
}
for (var i = 0; i < details.sharedByLogins.length; i++) {
details.sharedByLogins[i]['avatarURL'] = PathService.getAvatar(details.sharedByLogins[i]);
}
return details;
}
},
'update': {method: 'PATCH', cache: false, params: {statusId: '@statusId'}},
'announce': {method: 'PATCH', cache: false, params: {params: '@statusId'}},
'hideStatus': {
method: 'POST',
params: {statusId: '@statusId'},
cache: false,
url: '/tatami/rest/statuses/hide/:statusId'
}
});
}
})();
================================================
FILE: mobile/www/app/shared/services/UserService.js
================================================
(function() {
'use strict';
angular.module('tatami.services')
.factory('UserService', userService);
userService.$inject = ['$resource', 'PathService'];
function userService($resource, PathService) {
var responseTransform = function (users) {
users = angular.fromJson(users);
for (var i = 0; i < users.length; i++) {
users[i]['avatarURL'] = PathService.getAvatar(users[i]);
}
return users;
};
return $resource('/tatami/rest/users/:username', null,
{
'get': {
method: 'GET', params: {username: '@username'},
cache: false,
transformResponse: function (user) {
user = angular.fromJson(user);
user['avatarURL'] = PathService.getAvatar(user);
return user;
}
},
'query': {
method: 'GET',
isArray: true,
url: '/tatami/rest/users',
transformResponse: responseTransform
},
'getFollowing': {
method: 'GET',
isArray: true,
params: {username: '@username'},
url: '/tatami/rest/users/:username/friends',
transformResponse: responseTransform
},
'getFollowers': {
method: 'GET',
isArray: true,
params: {username: '@username'},
url: '/tatami/rest/users/:username/followers',
transformResponse: responseTransform
},
'getSuggestions': {
method: 'GET',
isArray: true,
url: '/tatami/rest/users/suggestions',
transformResponse: function (suggestions) {
suggestions = angular.fromJson(suggestions);
for (var i = 0; i < suggestions.length; i++) {
suggestions[i]['avatarURL'] = PathService.getAvatar(suggestions[i]);
suggestions[i]['followingUser'] = false;
}
return suggestions;
}
},
'follow': {
method: 'PATCH',
params: {username: '@username'}
},
'searchUsers': {
method: 'GET',
isArray: true,
url: '/tatami/rest/users/:term',
transformResponse: responseTransform
},
'deactivate': {
method: 'PATCH',
params: {username: '@username'}
}
});
}
})();
================================================
FILE: mobile/www/app/shared/services/account.service.js
================================================
(function() {
'use strict';
angular.module('tatami.services')
.factory('AccountService', accountService);
accountService.$inject = ['$resource', 'PathService'];
function accountService($resource, PathService) {
return $resource('/tatami/rest/account/admin', null, null);
}
})();
================================================
FILE: mobile/www/app/shared/services/block.service.js
================================================
(function() {
'use strict';
angular.module('tatami.services')
.factory('BlockService', blockService);
blockService.$inject = ['$resource', 'PathService'];
function blockService($resource, PathService) {
var responseTransform = function (users) {
users = angular.fromJson(users);
for (var i = 0; i < users.length; i++) {
users[i]['avatarURL'] = PathService.getAvatar(users[i]);
}
return users;
};
return $resource(null, null,
{
'getBlockedUsersForUser': {
method: 'GET',
isArray: true,
params: {username: '@username'},
url: '/tatami/rest/block/blockedusers/:username',
transformResponse: responseTransform
},
'updateBlockedUser': {
method: 'PATCH',
params: { username: '@username'},
url: '/tatami/rest/block/update/:username',
transformResponse: function (blockedUser) {
blockedUser = angular.fromJson(blockedUser);
return blockedUser;
}
}
});
}
})();
================================================
FILE: mobile/www/app/shared/services/localStorage.service.js
================================================
(function() {
'use strict';
angular.module('tatami.services')
.factory('$localStorage', localStorage);
localStorage.$inject = ['$window'];
function localStorage($window) {
var service = {
get: getFromLocalStorage,
set: setFromLocalStorage,
signOut: clearToken
};
return service;
getFromLocalStorage.$inject = ['key'];
function getFromLocalStorage(key) {
if(isLocalStorageUndefined(key)) {
return undefined;
}
return JSON.parse($window.localStorage[key] || '{}');
}
setFromLocalStorage.$inject = ['key', 'value'];
function setFromLocalStorage(key, value) {
$window.localStorage[key] = JSON.stringify(value);
}
function clearToken() {
$window.localStorage.removeItem('token');
}
isLocalStorageUndefined.$inject = ['key'];
function isLocalStorageUndefined(key) {
return $window.localStorage.length === 0 || $window.localStorage[key] === 'undefined';
}
}
})();
================================================
FILE: mobile/www/app/shared/services/path.service.js
================================================
(function() {
'use strict';
angular.module('tatami.services')
.factory('PathService', avatarService);
avatarService.$inject = ['TatamiEndpoint'];
function avatarService(TatamiEndpoint) {
var service = {
getAvatar: getAvatar
};
return service;
getAvatar.$inject = ['user'];
function getAvatar(user) {
return TatamiEndpoint.getEndpoint().url + (user.avatar && user.avatar !== '' ? '/tatami/avatar/' + user.avatar + '/photo.jpg' : '/assets/img/default_image_profile.png');
}
//buildPath() removed - REST endpoints being accessed were being cached, leading to issues when swapping endpoints
//new implementation intercepts url requests beginning with "/" and tacks on the current endpoint url as a prefix
}
})();
================================================
FILE: mobile/www/app/shared/services/report.service.js
================================================
(function() {
'use strict';
angular.module('tatami.services')
.factory('ReportService', reportService);
reportService.$inject = ['$resource', 'PathService'];
function reportService($resource, PathService) {
var responseTransform = function (statuses) {
statuses = angular.fromJson(statuses);
for (var i = 0; i < statuses.length; i++) {
statuses[i]['avatarURL'] = PathService.getAvatar(statuses[i]);
}
return statuses;
};
return $resource('/tatami/rest/statuses/report/:statusId', null,
{
'reportStatus': {
method: 'POST',
params: {statusId: '@statusId'}
},
'getReportedStatuses': {
method: 'GET',
isArray: true,
url: '/tatami/rest/statuses/report/reportedList',
transformResponse: responseTransform
},
'approveStatus': {
method : 'DELETE',
params: {statusId: '@statusId'}
},
'deleteStatus': {
method: 'PUT',
params: {statusId: '@statusId'}
}
});
}
})();
================================================
FILE: mobile/www/app/shared/services/service.js
================================================
(function() {
'use strict';
angular.module('tatami.services', []);
})();
================================================
FILE: mobile/www/app/shared/services/tag.service.js
================================================
(function() {
'use strict';
angular.module('tatami.services')
.factory('TagService', tagService);
tagService.$inject = ['$resource', 'PathService'];
function tagService($resource, PathService) {
return $resource('/tatami/rest/tags', null,
{
'get': { method:'GET', params: { tag: '@tag' }, url: '/tatami/rest/tags/:tag' },
'getTagTimeline': {
method:'GET', isArray: true, params: { tag: '@tag' }, url: '/tatami/rest/tags/:tag/tag_timeline',
transformResponse: function(statuses) {
statuses = angular.fromJson(statuses);
for(var i = 0; i < statuses.length; i++) {
statuses[i]['avatarURL'] = PathService.getAvatar(statuses[i]);
if(statuses[i].geoLocalization) {
var latitude = statuses[i].geoLocalization.split(',')[0].trim();
var longitude = statuses[i].geoLocalization.split(',')[1].trim();
statuses[i]['locationURL'] =
'https://www.openstreetmap.org/?mlon='
+ longitude + '&mlat=' + latitude;
}
}
return statuses;
}
},
'follow': { method:'PUT', params: { tag: '@tag' }, url: '/tatami/rest/tags/:tag' },
'getPopular': { method: 'GET', isArray: true, url: '/tatami/rest/tags/popular' }
});
}
})();
================================================
FILE: mobile/www/app/shared/services/toast.service.js
================================================
(function() {
'use strict';
angular.module('tatami.services')
.factory('ToastService', ToastService);
ToastService.$inject = ['$window', '$translate', 'ionicToast'];
function ToastService($window, $translate, ionicToast) {
var service = {
display: displayToast
};
return service;
function displayToast(toastMessage){
$translate(toastMessage).then(function(msg){
if (ionic.Platform.isIOS()){
ionicToast.show(msg, 'top', false, 2000);
}
else{
ionicToast.show(msg, 'bottom', false, 2000);
}
});
}
}
})();
================================================
FILE: mobile/www/app/shared/state/conversation/conversation.controller.js
================================================
(function() {
'use strict';
angular.module('tatami')
.controller('ConversationCtrl', conversationCtrl);
conversationCtrl.$inject = ['$ionicPopup', '$ionicHistory', '$state', 'originalStatus', 'conversation', 'currentUser'];
function conversationCtrl($ionicPopup, $ionicHistory, $state, originalStatus, conversation, currentUser) {
var vm = this;
vm.conversation = buildStatusList();
vm.currentUser = currentUser;
function buildStatusList() {
try {
return conversation.discussionStatuses.concat(originalStatus).sort(byDate);
} catch (error) {
var deletedPopup = $ionicPopup.alert({
title: 'Status Not Found!',
template: 'The original status has been deleted. Returning to previous state.'
});
deletedPopup.then(goBack);
}
}
function goBack() {
$ionicHistory.goBack();
}
byDate.$inject = ['first', 'second'];
function byDate(first, second) {
return first.statusDate - second.statusDate;
}
}
})();
================================================
FILE: mobile/www/app/shared/state/conversation/conversation.html
================================================
================================================
FILE: mobile/www/app/shared/state/profile/profile.controller.js
================================================
(function() {
'use strict';
angular.module('tatami')
.controller('ProfileCtrl', profileCtrl);
profileCtrl.$inject = ['$ionicPopover', '$ionicPopup', '$scope', '$translate', 'user', 'statuses', 'currentUser', 'TatamiStatusRefresherService', 'UserService', 'BlockService'];
function profileCtrl($ionicPopover, $ionicPopup, $scope, $translate, user, statuses, currentUser, TatamiStatusRefresherService, UserService, BlockService) {
var vm = this;
vm.user = user;
vm.statuses = statuses;
vm.currentUser = currentUser;
vm.isCurrentUser = (vm.currentUser.username === vm.user.username);
vm.customHeight = (vm.currentUser.isAdmin) ? {'height': '120px'} : {'height': '70px'}; //Adapts the height of the popover depending on the role because a different number of buttons is displayed
vm.followUser = followUser;
vm.getNewStatuses = getNewStatuses;
vm.toggleActivateUser = toggleActivateUser;
vm.blockUser = blockUser;
function getNewStatuses() {
return TatamiStatusRefresherService.refreshUserTimeline(user).then(setStatuses);
}
setStatuses.$inject = ['statuses'];
function setStatuses(statuses) {
vm.statuses = statuses;
}
function followUser() {
UserService.follow({ username : vm.user.username }, { friend: !vm.user.friend, friendShip: true },
function() {
vm.user.friend = !vm.user.friend;
});
}
function toggleActivateUser() {
var confirmPopup;
if (vm.user.activated) {
confirmPopup = $ionicPopup.confirm({
title: $translate.instant('user.deactivate.title'),
template: ' '
});
} else {
confirmPopup = $ionicPopup.confirm({
title: $translate.instant('user.reactivate.title'),
template: ' '
});
}
confirmPopup.then(checkDelete);
checkDelete.$inject = ['decision'];
function checkDelete(decision) {
if(decision) {
UserService.deactivate({ username : vm.user.username }, {activate: true},
function() {
vm.user.activated = !vm.user.activated;
});
}
}
}
function blockUser() {
var confirmPopup = $ionicPopup.confirm({
title: $translate.instant('user.block.title'),
template: ' '
});
confirmPopup.then(checkDelete);
checkDelete.$inject = ['decision'];
function checkDelete(decision) {
if(decision) {
BlockService.updateBlockedUser( {username: vm.user.username }, function () {
ToastService.display('user.block.success');
}
);
}
}
}
$ionicPopover.fromTemplateUrl('app/shared/state/profile/userOptionsMenu.html', {
scope: $scope
}).then(function(popover) {
$scope.popover = popover;
});
}
})();
================================================
FILE: mobile/www/app/shared/state/profile/profile.html
================================================
================================================
FILE: mobile/www/app/shared/state/profile/userOptionsMenu.html
================================================
================================================
FILE: mobile/www/app/shared/state/tag/tag.controller.js
================================================
(function() {
'use strict';
angular.module('tatami')
.controller('TagCtrl', tagCtrl);
tagCtrl.$inject = ['tag', 'statuses', 'currentUser', 'TatamiStatusRefresherService'];
function tagCtrl(tag, statuses, currentUser, TatamiStatusRefresherService) {
var vm = this;
vm.tag = tag;
vm.statuses = statuses;
vm.currentUser = currentUser;
vm.getNewStatuses = getNewStatuses;
vm.getOldStatuses = getOldStatuses;
function getNewStatuses() {
return TatamiStatusRefresherService.refreshTagTimeline(vm.tag);
}
getOldStatuses.$inject = ['finalStatus'];
function getOldStatuses(finalStatus) {
return TatamiStatusRefresherService.getOldTags(finalStatus, vm.tag)
}
}
})();
================================================
FILE: mobile/www/app/shared/state/tag/tag.html
================================================
================================================
FILE: mobile/www/app/shared/status/blockUserMenu.html
================================================
================================================
FILE: mobile/www/app/shared/status/list/status-list.html
================================================
================================================
FILE: mobile/www/app/shared/status/list/status.list.directive.js
================================================
(function() {
'use strict';
angular.module('tatami')
.directive('tatamiStatusList', tatamiStatusList);
function tatamiStatusList() {
var directive = {
restrict: 'E',
scope: {
statuses: '=',
currentUser: '=',
tatamiRefresher: '&',
tatamiInfiniteRefresher: '&'
},
controller: controller,
controllerAs: 'vm',
templateUrl: 'app/shared/status/list/status-list.html'
};
return directive;
}
controller.$inject = ['$scope', '$state'];
function controller($scope, $state) {
var vm = this;
vm.statuses = $scope.statuses;
vm.currentUser = $scope.currentUser;
vm.$state = $state;
vm.getNewStatuses = getNewStatuses;
vm.getNewInfiniteScrollStatuses = getNewInfiniteScrollStatuses;
vm.remove = remove;
vm.finishedTimeline = vm.statuses && vm.statuses.length < 20;
function getNewStatuses() {
$scope.tatamiRefresher().then(setStatuses);
}
function getNewInfiniteScrollStatuses() {
if(vm.statuses && vm.statuses.length > 0) {
var lastStatus = vm.statuses[vm.statuses.length - 1].timelineId;
$scope.tatamiInfiniteRefresher({ finalStatus: lastStatus }).then(addNewStatuses);
}
}
remove.$inject = ['status'];
function remove(status) {
vm.statuses.splice(vm.statuses.indexOf(status), 1);
}
setStatuses.$inject = ['statuses'];
function setStatuses(statuses) {
vm.statuses = statuses;
}
addNewStatuses.$inject = ['oldStatuses'];
function addNewStatuses(oldStatuses) {
if(oldStatuses.length === 0) {
vm.finishedTimeline = true;
}
vm.statuses.push.apply(vm.statuses, oldStatuses);
}
}
})();
================================================
FILE: mobile/www/app/shared/status/status.directive.js
================================================
(function() {
'use strict';
angular.module('tatami')
.directive('tatamiStatus', tatamiStatus);
function tatamiStatus() {
var directive = {
restrict: 'E',
scope: {
status: '=',
currentUser: '=',
onDelete: '&'
},
controller: controller,
controllerAs: 'vm',
templateUrl: 'app/shared/status/status.html'
};
return directive;
}
controller.$inject = ['$scope', '$state', '$ionicPopup', '$ionicPopover', '$filter', '$sce', 'StatusService', 'PathService', 'BlockService', '$translate', 'ReportService', 'ToastService', 'TatamiEndpoint'];
function controller($scope, $state, $ionicPopup, $ionicPopover, $filter, $sce, StatusService, PathService, BlockService, $translate, ReportService, ToastService, TatamiEndpoint) {
var vm = this;
vm.state = $state.current.name;
vm.status = $scope.status;
vm.status.content = $filter('markdown')(vm.status.content);
vm.currentUser = $scope.currentUser;
vm.isAdmin = $scope.currentUser.isAdmin;
vm.customHeight = (vm.isAdmin)? {'height': '270px'} : {'height': '170px'}; //Adapts the height of the popover depending on the role because a different number of buttons is displayed
vm.remove = remove;
vm.favorite = favorite;
vm.isCurrentUser = !vm.currentUser || vm.currentUser.username === vm.status.username;
vm.postReply = postReply;
vm.goToConversation = goToConversation;
vm.goToProfile = goToProfile;
vm.goToTagTimeline = goToTagTimeline;
vm.shareStatus = shareStatus;
vm.buildAttachmentUrl = buildAttachmentUrl;
vm.blockUser = blockUser;
vm.reportStatus = reportStatus;
vm.hideStatus = hideStatus;
vm.announceStatus = announceStatus;
vm.tatamEndpoint = TatamiEndpoint.getEndpoint().url;
function remove() {
var confirmPopup = $ionicPopup.confirm({
title: 'Delete',
template: ' '
});
confirmPopup.then(checkDelete);
checkDelete.$inject = ['decision'];
function checkDelete(decision) {
if(decision) {
StatusService.delete({ statusId : vm.status.statusId }, function() {
$scope.onDelete(vm.status);
});
}
}
}
function favorite() {
StatusService.update({ statusId: vm.status.statusId }, { favorite: !vm.status.favorite }, function (status) {
setStatus(status);
if(vm.status.favorite){
ToastService.display('status.favorite.add');
} else {
ToastService.display('status.favorite.remove');
}
});
}
function postReply() {
$state.go('post', { statusId : vm.status.statusId });
}
goToConversation.$inject = ['statusId'];
function goToConversation(statusId) {
var destinationState = $state.current.name.split('.')[0] + '.conversation';
$state.go(destinationState, { statusId : statusId });
}
goToProfile.$inject = ['username'];
function goToProfile(username) {
var destinationState = $state.current.name.split('.')[0] + '.profile';
$state.go(destinationState, { username : username });
}
goToTagTimeline.$inject = ['tag'];
function goToTagTimeline(tag) {
var destinationState = $state.current.name.split('.')[0] + '.tag';
$state.go(destinationState, { tag: tag });
}
function shareStatus() {
StatusService.update({statusId: vm.status.statusId}, {shared: !vm.status.shareByMe}, function(status){
setStatus(status);
ToastService.display('status.share.toast');
});
}
function reportStatus() {
var confirmPopup = $ionicPopup.confirm({
title: 'Report Status',
template: ' '
});
confirmPopup.then(reported);
reported.$inject = ['decision'];
function reported(decision) {
if(decision) {
ReportService.reportStatus({statusId: vm.status.statusId}, function () {
ToastService.display('status.reportStatus.toast');
}
);
}
}
}
setStatus.$inject = ['status'];
function setStatus(status) {
vm.status = status;
}
buildAttachmentUrl.$inject = ['attachment'];
function buildAttachmentUrl(attachment) {
var location = TatamiEndpoint.getEndpoint().url;
return location + '/tatami/file/' + attachment.attachmentId + '/' + attachment.filename;
}
$ionicPopover.fromTemplateUrl('app/shared/status/blockUserMenu.html', {
scope: $scope
}).then(function(popover) {
$scope.popover = popover;
});
function blockUser() {
var confirmPopup = $ionicPopup.confirm({
title: 'Block User',
template: ' '
});
confirmPopup.then(checkDelete);
checkDelete.$inject = ['decision'];
function checkDelete(decision) {
if(decision) {
BlockService.updateBlockedUser( {username: vm.status.username }, function () {
ToastService.display('user.block.success');
}
);
$state.go($state.current, {}, {reload: true});
// $scope.onDelete(vm.status);
}
}
}
function hideStatus() {
StatusService.hideStatus({statusId: vm.status.statusId}, function () {
$scope.onDelete(vm.status);
ToastService.display('status.hide.toast');
});
}
function announceStatus() {
StatusService.update({statusId: vm.status.statusId}, {announced: true}, function(){
setStatus;
ToastService.display('status.announcement.toast');
});
}
}
})();
================================================
FILE: mobile/www/app/shared/status/status.html
================================================
Your status has been shared
New follower
In reply to
@{{ vm.status.replyToUsername }}
{{ attachment.filename }}
================================================
FILE: mobile/www/app/shared/status/status.refresher.service.js
================================================
(function() {
'use strict';
angular.module('tatami')
.factory('TatamiStatusRefresherService', tatamiStatusRefresherService);
tatamiStatusRefresherService.$inject = ['$rootScope', 'StatusService', 'HomeService', 'TagService'];
function tatamiStatusRefresherService($rootScope, StatusService, HomeService, TagService) {
var service = {
refreshHomeTimeline: refreshHomeTimeline,
refreshCompanyTimeline: refreshCompanyTimeline,
refreshMentions: refreshMentions,
refreshFavorites: refreshFavorites,
refreshUserTimeline: refreshUserTimeline,
refreshTagTimeline: refreshTagTimeline,
getOldFromHomeTimeline: getOldFromHomeTimeline,
getOldFromCompanyTimeline: getOldFromCompanyTimeline,
getOldMentions: getOldMentions,
getOldTags: getOldTags
};
return service;
function refreshHomeTimeline() {
return StatusService.getHomeTimeline().$promise.then(updateStatuses);
}
function refreshCompanyTimeline() {
return HomeService.getCompanyTimeline().$promise.then(updateStatuses);
}
refreshUserTimeline.$inject = ['user'];
function refreshUserTimeline(user) {
return StatusService.getUserTimeline({ username : user.username }).$promise.then(updateStatuses);
}
function refreshMentions() {
return HomeService.getMentions().$promise.then(updateStatuses);
}
function refreshFavorites() {
return HomeService.getFavorites().$promise.then(updateStatuses);
}
refreshTagTimeline.$inject = ['tag'];
function refreshTagTimeline(tag) {
return TagService.getTagTimeline({ tag: tag }).$promise.then(updateStatuses);
}
getOldFromHomeTimeline.$inject = ['finalStatus'];
function getOldFromHomeTimeline(finalStatus) {
return StatusService.getHomeTimeline({ finish: finalStatus }).$promise.then(updateInfiniteStatuses);
}
getOldFromCompanyTimeline.$inject = ['finalStatus'];
function getOldFromCompanyTimeline(finalStatus) {
return HomeService.getCompanyTimeline({ finish: finalStatus }).$promise.then(updateInfiniteStatuses);
}
getOldMentions.$inject = ['finalStatus'];
function getOldMentions(finalStatus) {
return HomeService.getMentions({ finish: finalStatus }).$promise.then(updateInfiniteStatuses);
}
getOldTags.$inject = ['finalStatus', 'tag'];
function getOldTags(finalStatus, tag) {
return TagService.getTagTimeline({ tag: tag, finish: finalStatus }).$promise.then(updateInfiniteStatuses);
}
updateStatuses.$inject = ['statuses'];
function updateStatuses(statuses) {
$rootScope.$broadcast('scroll.refreshComplete');
return statuses;
}
updateInfiniteStatuses.$inject = ['statuses'];
function updateInfiniteStatuses(statuses) {
$rootScope.$broadcast('scroll.infiniteScrollComplete');
return statuses;
}
}
})();
================================================
FILE: mobile/www/app/shared/user/user-detail.html
================================================
{{ vm.user.firstName + ' ' + vm.user.lastName }}
@{{ vm.user.username }}
================================================
FILE: mobile/www/app/shared/user/user.detail.directive.js
================================================
(function() {
'use strict';
angular.module('tatami')
.directive('tatamiUserDetail', tatamiUserDetail);
function tatamiUserDetail() {
var directive = {
restrict: 'E',
scope: {
user: '='
},
controller: controller,
controllerAs: 'vm',
templateUrl: 'app/shared/user/user-detail.html'
};
return directive;
}
controller.$inject = ['$scope'];
function controller($scope) {
var vm = this;
vm.user = $scope.user;
}
})();
================================================
FILE: mobile/www/app/shared/user/user.directive.js
================================================
(function() {
'use strict';
angular.module('tatami')
.directive('tatamiUser', tatamiUser);
tatamiUser.$inject = [];
function tatamiUser() {
var directive = {
restrict: 'E',
scope: {
user: '=',
currentUser: '='
},
controller: controller,
controllerAs: 'vm',
templateUrl: 'app/shared/user/user.html'
};
return directive;
}
controller.$inject = ['$scope', '$state', 'UserService', 'BlockService', '$ionicPopup', '$ionicListDelegate'];
function controller($scope, $state, UserService, BlockService, $ionicPopup, $ionicListDelegate) {
var vm = this;
vm.currentUser = $scope.currentUser;
vm.user = $scope.user;
vm.$state = $state;
vm.followUser = followUser;
vm.goToProfile = goToProfile;
vm.updateBlockUser = updateBlockUser;
vm.toggleActivateUser = toggleActivateUser;
function followUser() {
UserService.follow({ username : vm.user.username }, { friend: !vm.user.friend, friendShip: true },
function() {
vm.user.friend = !vm.user.friend;
});
}
function goToProfile(username) {
var destinationState = $state.current.name.split('.')[0] + '.profile';
$state.go(destinationState, { username : username });
$ionicListDelegate.closeOptionButtons();
}
function updateBlockUser() {
BlockService.updateBlockedUser(
{username: vm.user.username },
function () {
if(vm.user.blocked){
$ionicPopup.alert({
template: ' '
});
} else{
$ionicPopup.alert({
template: ' '
});
}
vm.user.blocked = !vm.user.blocked;
}
);
$ionicListDelegate.closeOptionButtons();
}
function toggleActivateUser() {
var confirmPopup;
if(vm.user.activated){
confirmPopup = $ionicPopup.confirm({
title: 'Deactivate user',
template: ' '
});
} else {
confirmPopup = $ionicPopup.confirm({
title: 'Activate user',
template: ' '
});
}
confirmPopup.then(toggle);
toggle.$inject = ['decision'];
function toggle(decision) {
if(decision) {
UserService.deactivate({ username : vm.user.username }, {activate: true},
function() {
vm.user.activated = !vm.user.activated;
}
);
}
}
$ionicListDelegate.closeOptionButtons();
}
}
})();
================================================
FILE: mobile/www/app/shared/user/user.html
================================================
{{ vm.user.firstName }} {{ vm.user.lastName }}
@{{ vm.user.username }}
{{ (vm.user.friend ? 'user.unfollow' : 'user.follow') | translate }}
{{ (vm.user.blocked ? 'user.unblock.title' : 'user.block.title') | translate }}
{{ (vm.user.activated ? 'user.deactivate.title' : 'user.reactivate.title') | translate }}
================================================
FILE: mobile/www/app/shared/user/user.refresher.service.js
================================================
(function() {
'use strict';
angular.module('tatami.services')
.factory('TatamiUserRefresherService', tatamiUserRefresherService);
tatamiUserRefresherService.$inject = ['$rootScope', 'UserService'];
function tatamiUserRefresherService($rootScope, UserService) {
var service = {
refreshSuggested: refreshSuggested,
refreshFollowers: refreshFollowers,
refreshFollowing: refreshFollowing,
getNextUsers: getNextUsers,
updateInfiniteUsers: updateInfiniteUsers
};
return service;
function refreshSuggested() {
return UserService.getSuggestions().$promise.then(updateUsers);
}
refreshFollowers.$inject = ['currentUser'];
function refreshFollowers(currentUser) {
return UserService.getFollowers({ username: currentUser.username }).$promise.then(updateUsers);
}
refreshFollowing.$inject = ['currentUser'];
function refreshFollowing(currentUser) {
return UserService.getFollowing({ username: currentUser.username }).$promise.then(updateUsers);
}
updateUsers.$inject = ['users'];
function updateUsers(users) {
$rootScope.$broadcast('scroll.refreshComplete');
return users;
}
getNextUsers.$inject = ['usersCount'];
function getNextUsers(usersCount) {
return UserService.query({ pagination: usersCount }).$promise.then(updateInfiniteUsers);
}
updateInfiniteUsers.$inject = ['users'];
function updateInfiniteUsers(users) {
$rootScope.$broadcast('scroll.infiniteScrollComplete');
return users;
}
}
})();
================================================
FILE: mobile/www/app/shared/user/users.directive.js
================================================
(function() {
'use strict';
angular.module('tatami')
.directive('tatamiUser', tatamiUser);
tatamiUser.$inject = ['$state'];
function tatamiUser($state) {
var directive = {
restrict: 'E',
scope: {
user: '='
},
controller: controller,
controllerAs: 'vm',
templateUrl: 'app/shared/user/users.html'
};
return directive;
}
controller.$inject = ['$scope', '$state', 'UserService'];
function controller($scope, $state, UserService) {
var vm = this;
vm.user = $scope.user;
vm.followUser = followUser;
function followUser() {
UserService.follow({ username : vm.user.username }, { friend: !vm.user.friend, friendShip: true },
function() {
$state.reload();
});
}
}
})();
================================================
FILE: mobile/www/app/shared/user/users.html
================================================
{{ vm.user.firstName }} {{ vm.user.lastName }}
@{{ vm.user.username }}
================================================
FILE: mobile/www/app/tatami.controller.js
================================================
(function() {
'use strict';
angular.module('tatami')
.controller('TatamiCtrl', tatamiCtrl);
tatamiCtrl.$inject = ['$state'];
function tatamiCtrl($state) {
var vm = this;
vm.$state = $state;
}
})();
================================================
FILE: mobile/www/app/tatami.endpoint.js
================================================
(function() {
'use strict';
var defaultEndpoint = {url: 'http://tatami.ippon.fr'};
var endpoint;
angular.module('tatami')
.factory('TatamiEndpoint', tatamiEndpoint);
tatamiEndpoint.$inject = ['$localStorage'];
function tatamiEndpoint($localStorage) {
var service = {
getEndpoint: getEndpoint,
setEndpoint: setEndpoint,
getDefault: getDefaultEndpoint,
reset: reset
};
endpoint = $localStorage.get('endpoint');
if(!endpoint || !endpoint.url) {
$localStorage.signOut();
endpoint = {url: defaultEndpoint.url};
$localStorage.set('endpoint', endpoint);
}
return service;
function getEndpoint() {
return endpoint;
}
setEndpoint.$inject = ['updated'];
function setEndpoint(updated) {
endpoint.url = updated;
$localStorage.set('endpoint', endpoint);
}
function getDefaultEndpoint() {
return defaultEndpoint;
}
function reset() {
setEndpoint(defaultEndpoint.url);
}
}
})();
================================================
FILE: mobile/www/app/tatami.html
================================================
================================================
FILE: mobile/www/app/tatamiApp.js
================================================
(function() {
'use strict';
angular.module('tatami', [
'ionic',
'tatami.services',
'tatami.providers',
'ngResource',
'ngCordova',
'hc.marked',
'pascalprecht.translate',
'ionic-toast'
]);
angular.module('tatami')
.run(tatamiRun)
.config(tatamiConfig);
tatamiRun.$inject = ['$ionicPlatform', '$state', '$localStorage', '$ionicHistory', '$translate', 'PathService', 'TatamiEndpoint', '$http'];
function tatamiRun($ionicPlatform, $state, $localStorage, $ionicHistory, $translate, PathService, TatamiEndpoint, $http) {
$ionicPlatform.ready(function () {
// Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
// for form inputs)
if (window.cordova && window.cordova.plugins && window.cordova.plugins.Keyboard) {
cordova.plugins.Keyboard.disableScroll(true);
}
if (window.StatusBar) {
// org.apache.cordova.statusbar required
if ($ionicPlatform.is('android')) {
StatusBar.backgroundColorByHexString('#444444');
} else {
StatusBar.backgroundColorByName('white');
}
}
// first, the endpoint (when TatamiEndpoint is injected) gets set up or reset
// based on whether or not it's been set before
// then, we check is the (new) default endpoint live?
$http({
url: '/tatami/rest/client/id',
method: 'GET'
}).then(function(result) {
// if the endpoint is live...
if (isValidToken()) {
// ...and the token is valid, go to the timeline
$state.go('timeline');
} else {
// ...and the token is invalid, trash it and log the user out
$localStorage.signOut();
logout();
}
}, function(result) {
// if the endpoint is invalid/down, the token is worthless now, so trash it,
// log out, and reset endpoint to default
$localStorage.signOut();
TatamiEndpoint.reset();
logout();
});
});
$ionicPlatform.on('resume', function resume() {
if (!isValidToken()) {
$localStorage.signOut();
logout();
}
});
function logout() {
$localStorage.signOut();
$ionicHistory.clearCache();
$state.go('login');
}
function isValidToken() {
var token = $localStorage.get('token');
return token && token.expires && token.expires > new Date().getTime()
}
var absolutePathChecker = new RegExp('^(?:[a-z]+:)?//', 'i');
document.onclick = function (e) {
e = e || window.event;
var element = e.target || e.srcElement;
if (element.tagName == 'A' && absolutePathChecker.test(element.href)) {
window.open(element.href, "_blank", "location=yes,presentationstyle=pagesheet,EnableViewPortScale=yes");
return false;
}
};
}
tatamiConfig.$inject = [
'$resourceProvider',
'$stateProvider',
'$translateProvider',
'$compileProvider',
'$httpProvider'
];
function tatamiConfig($resourceProvider, $stateProvider, $translateProvider, $compileProvider, $httpProvider) {
$compileProvider.imgSrcSanitizationWhitelist(/^\s*(https?|ftp|mailto|file|tel):/);
$resourceProvider.defaults.stripTrailingSlashes = false;
$stateProvider
// setup an abstract state for the tabs directive
.state('tatami', {
url: '',
abstract: true,
templateUrl: 'app/tatami.html',
controller: 'TatamiCtrl',
controllerAs: 'vm',
resolve: {
translatePartialLoader: getTranslationPartialLoader
}
});
getTranslationPartialLoader.$inject = ['$translate', '$translatePartialLoader'];
function getTranslationPartialLoader($translate, $translatePartialLoader) {
$translatePartialLoader.addPart('user');
$translatePartialLoader.addPart('status');
$translatePartialLoader.addPart('conversation');
return $translate.refresh();
}
$httpProvider.interceptors.push('authInterceptor');
$httpProvider.interceptors.push('authExpiredInterceptor');
$httpProvider.interceptors.push('endpointInterceptor');
$translateProvider.useLoader('$translatePartialLoader', {
urlTemplate: 'i18n/{lang}/{part}.json'
});
var usedLanguage = window.localStorage.getItem('language') || navigator.language.split('-')[0] || 'en';
window.localStorage.setItem('language', usedLanguage);
$translateProvider.preferredLanguage(usedLanguage);
$translateProvider.use(usedLanguage);
$translateProvider.useSanitizeValueStrategy('escaped');
$translateProvider.addInterpolation('$translateMessageFormatInterpolation');
$compileProvider.directive('compile', compile);
compile.$inject = ['$compile'];
function compile($compile) {
return directive;
directive.$inject = ['scope', 'element', 'attrs'];
function directive(scope, element, attrs) {
var ensureCompileRunsOnce = scope.$watch(
function(scope) {
return scope.$eval(attrs.compile);
},
function(value) {
element.html(value);
$compile(element.contents())(scope);
ensureCompileRunsOnce();
}
);
}
}
}
})();
================================================
FILE: mobile/www/css/ionic.app.css
================================================
@charset "UTF-8";
/*
To customize the look and feel of Ionic, you can override the variables
in ionic's _variables.scss file.
For example, you might change some of the default colors:
$light: #fff !default;
$stable: #f8f8f8 !default;
$positive: #387ef5 !default;
$calm: #11c1f3 !default;
$balanced: #33cd5f !default;
$energized: #ffc900 !default;
$assertive: #ef473a !default;
$royal: #886aea !default;
$dark: #444 !default;
*/
/*!
* Copyright 2015 Drifty Co.
* http://drifty.com/
*
* Ionic, v1.2.4
* A powerful HTML5 mobile app framework.
* http://ionicframework.com/
*
* By @maxlynch, @benjsperry, @adamdbradley <3
*
* Licensed under the MIT license. Please see LICENSE for more information.
*
*/
/*!
Ionicons, v2.0.1
Created by Ben Sperry for the Ionic Framework, http://ionicons.com/
https://twitter.com/benjsperry https://twitter.com/ionicframework
MIT License: https://github.com/driftyco/ionicons
Android-style icons originally built by Google’s
Material Design Icons: https://github.com/google/material-design-icons
used under CC BY http://creativecommons.org/licenses/by/4.0/
Modified icons to fit ionicon’s grid from original.
*/
@font-face {
font-family: "Ionicons";
src: url("../fonts/ionicons.eot?v=2.0.1");
src: url("../fonts/ionicons.eot?v=2.0.1#iefix") format("embedded-opentype"), url("../fonts/ionicons.ttf?v=2.0.1") format("truetype"), url("../fonts/ionicons.woff?v=2.0.1") format("woff"), url("../fonts/ionicons.woff") format("woff"), url("../fonts/ionicons.svg?v=2.0.1#Ionicons") format("svg");
font-weight: normal;
font-style: normal; }
.ion, .ionicons,
.ion-alert:before,
.ion-alert-circled:before,
.ion-android-add:before,
.ion-android-add-circle:before,
.ion-android-alarm-clock:before,
.ion-android-alert:before,
.ion-android-apps:before,
.ion-android-archive:before,
.ion-android-arrow-back:before,
.ion-android-arrow-down:before,
.ion-android-arrow-dropdown:before,
.ion-android-arrow-dropdown-circle:before,
.ion-android-arrow-dropleft:before,
.ion-android-arrow-dropleft-circle:before,
.ion-android-arrow-dropright:before,
.ion-android-arrow-dropright-circle:before,
.ion-android-arrow-dropup:before,
.ion-android-arrow-dropup-circle:before,
.ion-android-arrow-forward:before,
.ion-android-arrow-up:before,
.ion-android-attach:before,
.ion-android-bar:before,
.ion-android-bicycle:before,
.ion-android-boat:before,
.ion-android-bookmark:before,
.ion-android-bulb:before,
.ion-android-bus:before,
.ion-android-calendar:before,
.ion-android-call:before,
.ion-android-camera:before,
.ion-android-cancel:before,
.ion-android-car:before,
.ion-android-cart:before,
.ion-android-chat:before,
.ion-android-checkbox:before,
.ion-android-checkbox-blank:before,
.ion-android-checkbox-outline:before,
.ion-android-checkbox-outline-blank:before,
.ion-android-checkmark-circle:before,
.ion-android-clipboard:before,
.ion-android-close:before,
.ion-android-cloud:before,
.ion-android-cloud-circle:before,
.ion-android-cloud-done:before,
.ion-android-cloud-outline:before,
.ion-android-color-palette:before,
.ion-android-compass:before,
.ion-android-contact:before,
.ion-android-contacts:before,
.ion-android-contract:before,
.ion-android-create:before,
.ion-android-delete:before,
.ion-android-desktop:before,
.ion-android-document:before,
.ion-android-done:before,
.ion-android-done-all:before,
.ion-android-download:before,
.ion-android-drafts:before,
.ion-android-exit:before,
.ion-android-expand:before,
.ion-android-favorite:before,
.ion-android-favorite-outline:before,
.ion-android-film:before,
.ion-android-folder:before,
.ion-android-folder-open:before,
.ion-android-funnel:before,
.ion-android-globe:before,
.ion-android-hand:before,
.ion-android-hangout:before,
.ion-android-happy:before,
.ion-android-home:before,
.ion-android-image:before,
.ion-android-laptop:before,
.ion-android-list:before,
.ion-android-locate:before,
.platform-android .tatami-location:before,
.ion-android-lock:before,
.ion-android-mail:before,
.ion-android-map:before,
.ion-android-menu:before,
.ion-android-microphone:before,
.ion-android-microphone-off:before,
.ion-android-more-horizontal:before,
.ion-android-more-vertical:before,
.ion-android-navigate:before,
.ion-android-notifications:before,
.ion-android-notifications-none:before,
.ion-android-notifications-off:before,
.ion-android-open:before,
.ion-android-options:before,
.ion-android-people:before,
.ion-android-person:before,
.ion-android-person-add:before,
.ion-android-phone-landscape:before,
.ion-android-phone-portrait:before,
.ion-android-pin:before,
.ion-android-plane:before,
.ion-android-playstore:before,
.ion-android-print:before,
.ion-android-radio-button-off:before,
.ion-android-radio-button-on:before,
.ion-android-refresh:before,
.ion-android-remove:before,
.ion-android-remove-circle:before,
.ion-android-restaurant:before,
.ion-android-sad:before,
.ion-android-search:before,
.ion-android-send:before,
.ion-android-settings:before,
.ion-android-share:before,
.ion-android-share-alt:before,
.ion-android-star:before,
.ion-android-star-half:before,
.ion-android-star-outline:before,
.ion-android-stopwatch:before,
.ion-android-subway:before,
.ion-android-sunny:before,
.ion-android-sync:before,
.ion-android-textsms:before,
.ion-android-time:before,
.ion-android-train:before,
.ion-android-unlock:before,
.ion-android-upload:before,
.ion-android-volume-down:before,
.ion-android-volume-mute:before,
.ion-android-volume-off:before,
.ion-android-volume-up:before,
.ion-android-walk:before,
.ion-android-warning:before,
.ion-android-watch:before,
.ion-android-wifi:before,
.ion-aperture:before,
.ion-archive:before,
.ion-arrow-down-a:before,
.ion-arrow-down-b:before,
.ion-arrow-down-c:before,
.ion-arrow-expand:before,
.ion-arrow-graph-down-left:before,
.ion-arrow-graph-down-right:before,
.ion-arrow-graph-up-left:before,
.ion-arrow-graph-up-right:before,
.ion-arrow-left-a:before,
.ion-arrow-left-b:before,
.ion-arrow-left-c:before,
.ion-arrow-move:before,
.ion-arrow-resize:before,
.ion-arrow-return-left:before,
.ion-arrow-return-right:before,
.ion-arrow-right-a:before,
.ion-arrow-right-b:before,
.ion-arrow-right-c:before,
.ion-arrow-shrink:before,
.ion-arrow-swap:before,
.ion-arrow-up-a:before,
.ion-arrow-up-b:before,
.ion-arrow-up-c:before,
.ion-asterisk:before,
.ion-at:before,
.ion-backspace:before,
.ion-backspace-outline:before,
.ion-bag:before,
.ion-battery-charging:before,
.ion-battery-empty:before,
.ion-battery-full:before,
.ion-battery-half:before,
.ion-battery-low:before,
.ion-beaker:before,
.ion-beer:before,
.ion-bluetooth:before,
.ion-bonfire:before,
.ion-bookmark:before,
.ion-bowtie:before,
.ion-briefcase:before,
.ion-bug:before,
.ion-calculator:before,
.ion-calendar:before,
.ion-camera:before,
.ion-card:before,
.ion-cash:before,
.ion-chatbox:before,
.ion-chatbox-working:before,
.ion-chatboxes:before,
.ion-chatbubble:before,
.ion-chatbubble-working:before,
.ion-chatbubbles:before,
.ion-checkmark:before,
.ion-checkmark-circled:before,
.ion-checkmark-round:before,
.ion-chevron-down:before,
.ion-chevron-left:before,
.ion-chevron-right:before,
.ion-chevron-up:before,
.ion-clipboard:before,
.ion-clock:before,
.ion-close:before,
.ion-close-circled:before,
.ion-close-round:before,
.ion-closed-captioning:before,
.ion-cloud:before,
.ion-code:before,
.ion-code-download:before,
.ion-code-working:before,
.ion-coffee:before,
.ion-compass:before,
.ion-compose:before,
.ion-connection-bars:before,
.ion-contrast:before,
.ion-crop:before,
.ion-cube:before,
.ion-disc:before,
.ion-document:before,
.ion-document-text:before,
.ion-drag:before,
.ion-earth:before,
.ion-easel:before,
.ion-edit:before,
.ion-egg:before,
.ion-eject:before,
.ion-email:before,
.ion-email-unread:before,
.ion-erlenmeyer-flask:before,
.ion-erlenmeyer-flask-bubbles:before,
.ion-eye:before,
.ion-eye-disabled:before,
.ion-female:before,
.ion-filing:before,
.ion-film-marker:before,
.ion-fireball:before,
.ion-flag:before,
.ion-flame:before,
.ion-flash:before,
.ion-flash-off:before,
.ion-folder:before,
.ion-fork:before,
.ion-fork-repo:before,
.ion-forward:before,
.ion-funnel:before,
.ion-gear-a:before,
.ion-gear-b:before,
.ion-grid:before,
.ion-hammer:before,
.ion-happy:before,
.ion-happy-outline:before,
.ion-headphone:before,
.ion-heart:before,
.ion-heart-broken:before,
.ion-help:before,
.ion-help-buoy:before,
.ion-help-circled:before,
.ion-home:before,
.ion-icecream:before,
.ion-image:before,
.ion-images:before,
.ion-information:before,
.ion-information-circled:before,
.ion-ionic:before,
.ion-ios-alarm:before,
.ion-ios-alarm-outline:before,
.ion-ios-albums:before,
.ion-ios-albums-outline:before,
.ion-ios-americanfootball:before,
.ion-ios-americanfootball-outline:before,
.ion-ios-analytics:before,
.ion-ios-analytics-outline:before,
.ion-ios-arrow-back:before,
.ion-ios-arrow-down:before,
.ion-ios-arrow-forward:before,
.ion-ios-arrow-left:before,
.ion-ios-arrow-right:before,
.ion-ios-arrow-thin-down:before,
.ion-ios-arrow-thin-left:before,
.ion-ios-arrow-thin-right:before,
.ion-ios-arrow-thin-up:before,
.ion-ios-arrow-up:before,
.ion-ios-at:before,
.ion-ios-at-outline:before,
.ion-ios-barcode:before,
.ion-ios-barcode-outline:before,
.ion-ios-baseball:before,
.ion-ios-baseball-outline:before,
.ion-ios-basketball:before,
.ion-ios-basketball-outline:before,
.ion-ios-bell:before,
.ion-ios-bell-outline:before,
.ion-ios-body:before,
.ion-ios-body-outline:before,
.ion-ios-bolt:before,
.ion-ios-bolt-outline:before,
.ion-ios-book:before,
.ion-ios-book-outline:before,
.ion-ios-bookmarks:before,
.ion-ios-bookmarks-outline:before,
.ion-ios-box:before,
.ion-ios-box-outline:before,
.ion-ios-briefcase:before,
.ion-ios-briefcase-outline:before,
.ion-ios-browsers:before,
.ion-ios-browsers-outline:before,
.ion-ios-calculator:before,
.ion-ios-calculator-outline:before,
.ion-ios-calendar:before,
.ion-ios-calendar-outline:before,
.ion-ios-camera:before,
.ion-ios-camera-outline:before,
.ion-ios-cart:before,
.ion-ios-cart-outline:before,
.ion-ios-chatboxes:before,
.ion-ios-chatboxes-outline:before,
.ion-ios-chatbubble:before,
.ion-ios-chatbubble-outline:before,
.ion-ios-checkmark:before,
.ion-ios-checkmark-empty:before,
.ion-ios-checkmark-outline:before,
.ion-ios-circle-filled:before,
.ion-ios-circle-outline:before,
.ion-ios-clock:before,
.ion-ios-clock-outline:before,
.ion-ios-close:before,
.ion-ios-close-empty:before,
.ion-ios-close-outline:before,
.ion-ios-cloud:before,
.ion-ios-cloud-download:before,
.ion-ios-cloud-download-outline:before,
.ion-ios-cloud-outline:before,
.ion-ios-cloud-upload:before,
.ion-ios-cloud-upload-outline:before,
.ion-ios-cloudy:before,
.ion-ios-cloudy-night:before,
.ion-ios-cloudy-night-outline:before,
.ion-ios-cloudy-outline:before,
.ion-ios-cog:before,
.ion-ios-cog-outline:before,
.ion-ios-color-filter:before,
.ion-ios-color-filter-outline:before,
.ion-ios-color-wand:before,
.ion-ios-color-wand-outline:before,
.ion-ios-compose:before,
.ion-ios-compose-outline:before,
.ion-ios-contact:before,
.ion-ios-contact-outline:before,
.ion-ios-copy:before,
.ion-ios-copy-outline:before,
.ion-ios-crop:before,
.ion-ios-crop-strong:before,
.ion-ios-download:before,
.ion-ios-download-outline:before,
.ion-ios-drag:before,
.ion-ios-email:before,
.ion-ios-email-outline:before,
.ion-ios-eye:before,
.ion-ios-eye-outline:before,
.ion-ios-fastforward:before,
.ion-ios-fastforward-outline:before,
.ion-ios-filing:before,
.ion-ios-filing-outline:before,
.ion-ios-film:before,
.ion-ios-film-outline:before,
.ion-ios-flag:before,
.ion-ios-flag-outline:before,
.ion-ios-flame:before,
.ion-ios-flame-outline:before,
.ion-ios-flask:before,
.ion-ios-flask-outline:before,
.ion-ios-flower:before,
.ion-ios-flower-outline:before,
.ion-ios-folder:before,
.ion-ios-folder-outline:before,
.ion-ios-football:before,
.ion-ios-football-outline:before,
.ion-ios-game-controller-a:before,
.ion-ios-game-controller-a-outline:before,
.ion-ios-game-controller-b:before,
.ion-ios-game-controller-b-outline:before,
.ion-ios-gear:before,
.ion-ios-gear-outline:before,
.ion-ios-glasses:before,
.ion-ios-glasses-outline:before,
.ion-ios-grid-view:before,
.ion-ios-grid-view-outline:before,
.ion-ios-heart:before,
.ion-ios-heart-outline:before,
.ion-ios-help:before,
.ion-ios-help-empty:before,
.ion-ios-help-outline:before,
.ion-ios-home:before,
.ion-ios-home-outline:before,
.ion-ios-infinite:before,
.ion-ios-infinite-outline:before,
.ion-ios-information:before,
.ion-ios-information-empty:before,
.ion-ios-information-outline:before,
.ion-ios-ionic-outline:before,
.ion-ios-keypad:before,
.ion-ios-keypad-outline:before,
.ion-ios-lightbulb:before,
.ion-ios-lightbulb-outline:before,
.ion-ios-list:before,
.ion-ios-list-outline:before,
.ion-ios-location:before,
.platform-ios .tatami-location:before,
.ion-ios-location-outline:before,
.ion-ios-locked:before,
.ion-ios-locked-outline:before,
.ion-ios-loop:before,
.ion-ios-loop-strong:before,
.ion-ios-medical:before,
.ion-ios-medical-outline:before,
.ion-ios-medkit:before,
.ion-ios-medkit-outline:before,
.ion-ios-mic:before,
.ion-ios-mic-off:before,
.ion-ios-mic-outline:before,
.ion-ios-minus:before,
.ion-ios-minus-empty:before,
.ion-ios-minus-outline:before,
.ion-ios-monitor:before,
.ion-ios-monitor-outline:before,
.ion-ios-moon:before,
.ion-ios-moon-outline:before,
.ion-ios-more:before,
.ion-ios-more-outline:before,
.ion-ios-musical-note:before,
.ion-ios-musical-notes:before,
.ion-ios-navigate:before,
.ion-ios-navigate-outline:before,
.ion-ios-nutrition:before,
.ion-ios-nutrition-outline:before,
.ion-ios-paper:before,
.ion-ios-paper-outline:before,
.ion-ios-paperplane:before,
.ion-ios-paperplane-outline:before,
.ion-ios-partlysunny:before,
.ion-ios-partlysunny-outline:before,
.ion-ios-pause:before,
.ion-ios-pause-outline:before,
.ion-ios-paw:before,
.ion-ios-paw-outline:before,
.ion-ios-people:before,
.ion-ios-people-outline:before,
.ion-ios-person:before,
.ion-ios-person-outline:before,
.ion-ios-personadd:before,
.ion-ios-personadd-outline:before,
.ion-ios-photos:before,
.ion-ios-photos-outline:before,
.ion-ios-pie:before,
.ion-ios-pie-outline:before,
.ion-ios-pint:before,
.ion-ios-pint-outline:before,
.ion-ios-play:before,
.ion-ios-play-outline:before,
.ion-ios-plus:before,
.ion-ios-plus-empty:before,
.ion-ios-plus-outline:before,
.ion-ios-pricetag:before,
.ion-ios-pricetag-outline:before,
.ion-ios-pricetags:before,
.ion-ios-pricetags-outline:before,
.ion-ios-printer:before,
.ion-ios-printer-outline:before,
.ion-ios-pulse:before,
.ion-ios-pulse-strong:before,
.ion-ios-rainy:before,
.ion-ios-rainy-outline:before,
.ion-ios-recording:before,
.ion-ios-recording-outline:before,
.ion-ios-redo:before,
.ion-ios-redo-outline:before,
.ion-ios-refresh:before,
.ion-ios-refresh-empty:before,
.ion-ios-refresh-outline:before,
.ion-ios-reload:before,
.ion-ios-reverse-camera:before,
.ion-ios-reverse-camera-outline:before,
.ion-ios-rewind:before,
.ion-ios-rewind-outline:before,
.ion-ios-rose:before,
.ion-ios-rose-outline:before,
.ion-ios-search:before,
.ion-ios-search-strong:before,
.ion-ios-settings:before,
.ion-ios-settings-strong:before,
.ion-ios-shuffle:before,
.ion-ios-shuffle-strong:before,
.ion-ios-skipbackward:before,
.ion-ios-skipbackward-outline:before,
.ion-ios-skipforward:before,
.ion-ios-skipforward-outline:before,
.ion-ios-snowy:before,
.ion-ios-speedometer:before,
.ion-ios-speedometer-outline:before,
.ion-ios-star:before,
.ion-ios-star-half:before,
.ion-ios-star-outline:before,
.ion-ios-stopwatch:before,
.ion-ios-stopwatch-outline:before,
.ion-ios-sunny:before,
.ion-ios-sunny-outline:before,
.ion-ios-telephone:before,
.ion-ios-telephone-outline:before,
.ion-ios-tennisball:before,
.ion-ios-tennisball-outline:before,
.ion-ios-thunderstorm:before,
.ion-ios-thunderstorm-outline:before,
.ion-ios-time:before,
.ion-ios-time-outline:before,
.ion-ios-timer:before,
.ion-ios-timer-outline:before,
.ion-ios-toggle:before,
.ion-ios-toggle-outline:before,
.ion-ios-trash:before,
.ion-ios-trash-outline:before,
.ion-ios-undo:before,
.ion-ios-undo-outline:before,
.ion-ios-unlocked:before,
.ion-ios-unlocked-outline:before,
.ion-ios-upload:before,
.ion-ios-upload-outline:before,
.ion-ios-videocam:before,
.ion-ios-videocam-outline:before,
.ion-ios-volume-high:before,
.ion-ios-volume-low:before,
.ion-ios-wineglass:before,
.ion-ios-wineglass-outline:before,
.ion-ios-world:before,
.ion-ios-world-outline:before,
.ion-ipad:before,
.ion-iphone:before,
.ion-ipod:before,
.ion-jet:before,
.ion-key:before,
.ion-knife:before,
.ion-laptop:before,
.ion-leaf:before,
.ion-levels:before,
.ion-lightbulb:before,
.ion-link:before,
.ion-load-a:before,
.ion-load-b:before,
.ion-load-c:before,
.ion-load-d:before,
.ion-location:before,
.ion-lock-combination:before,
.ion-locked:before,
.ion-log-in:before,
.ion-log-out:before,
.ion-loop:before,
.ion-magnet:before,
.ion-male:before,
.ion-man:before,
.ion-map:before,
.ion-medkit:before,
.ion-merge:before,
.ion-mic-a:before,
.ion-mic-b:before,
.ion-mic-c:before,
.ion-minus:before,
.ion-minus-circled:before,
.ion-minus-round:before,
.ion-model-s:before,
.ion-monitor:before,
.ion-more:before,
.ion-mouse:before,
.ion-music-note:before,
.ion-navicon:before,
.ion-navicon-round:before,
.ion-navigate:before,
.ion-network:before,
.ion-no-smoking:before,
.ion-nuclear:before,
.ion-outlet:before,
.ion-paintbrush:before,
.ion-paintbucket:before,
.ion-paper-airplane:before,
.ion-paperclip:before,
.ion-pause:before,
.ion-person:before,
.ion-person-add:before,
.ion-person-stalker:before,
.ion-pie-graph:before,
.ion-pin:before,
.ion-pinpoint:before,
.ion-pizza:before,
.ion-plane:before,
.ion-planet:before,
.ion-play:before,
.ion-playstation:before,
.ion-plus:before,
.ion-plus-circled:before,
.ion-plus-round:before,
.ion-podium:before,
.ion-pound:before,
.ion-power:before,
.ion-pricetag:before,
.ion-pricetags:before,
.ion-printer:before,
.ion-pull-request:before,
.ion-qr-scanner:before,
.ion-quote:before,
.ion-radio-waves:before,
.ion-record:before,
.ion-refresh:before,
.ion-reply:before,
.ion-reply-all:before,
.ion-ribbon-a:before,
.ion-ribbon-b:before,
.ion-sad:before,
.ion-sad-outline:before,
.ion-scissors:before,
.ion-search:before,
.ion-settings:before,
.ion-share:before,
.ion-shuffle:before,
.ion-skip-backward:before,
.ion-skip-forward:before,
.ion-social-android:before,
.ion-social-android-outline:before,
.ion-social-angular:before,
.ion-social-angular-outline:before,
.ion-social-apple:before,
.ion-social-apple-outline:before,
.ion-social-bitcoin:before,
.ion-social-bitcoin-outline:before,
.ion-social-buffer:before,
.ion-social-buffer-outline:before,
.ion-social-chrome:before,
.ion-social-chrome-outline:before,
.ion-social-codepen:before,
.ion-social-codepen-outline:before,
.ion-social-css3:before,
.ion-social-css3-outline:before,
.ion-social-designernews:before,
.ion-social-designernews-outline:before,
.ion-social-dribbble:before,
.ion-social-dribbble-outline:before,
.ion-social-dropbox:before,
.ion-social-dropbox-outline:before,
.ion-social-euro:before,
.ion-social-euro-outline:before,
.ion-social-facebook:before,
.ion-social-facebook-outline:before,
.ion-social-foursquare:before,
.ion-social-foursquare-outline:before,
.ion-social-freebsd-devil:before,
.ion-social-github:before,
.ion-social-github-outline:before,
.ion-social-google:before,
.ion-social-google-outline:before,
.ion-social-googleplus:before,
.ion-social-googleplus-outline:before,
.ion-social-hackernews:before,
.ion-social-hackernews-outline:before,
.ion-social-html5:before,
.ion-social-html5-outline:before,
.ion-social-instagram:before,
.ion-social-instagram-outline:before,
.ion-social-javascript:before,
.ion-social-javascript-outline:before,
.ion-social-linkedin:before,
.ion-social-linkedin-outline:before,
.ion-social-markdown:before,
.ion-social-nodejs:before,
.ion-social-octocat:before,
.ion-social-pinterest:before,
.ion-social-pinterest-outline:before,
.ion-social-python:before,
.ion-social-reddit:before,
.ion-social-reddit-outline:before,
.ion-social-rss:before,
.ion-social-rss-outline:before,
.ion-social-sass:before,
.ion-social-skype:before,
.ion-social-skype-outline:before,
.ion-social-snapchat:before,
.ion-social-snapchat-outline:before,
.ion-social-tumblr:before,
.ion-social-tumblr-outline:before,
.ion-social-tux:before,
.ion-social-twitch:before,
.ion-social-twitch-outline:before,
.ion-social-twitter:before,
.ion-social-twitter-outline:before,
.ion-social-usd:before,
.ion-social-usd-outline:before,
.ion-social-vimeo:before,
.ion-social-vimeo-outline:before,
.ion-social-whatsapp:before,
.ion-social-whatsapp-outline:before,
.ion-social-windows:before,
.ion-social-windows-outline:before,
.ion-social-wordpress:before,
.ion-social-wordpress-outline:before,
.ion-social-yahoo:before,
.ion-social-yahoo-outline:before,
.ion-social-yen:before,
.ion-social-yen-outline:before,
.ion-social-youtube:before,
.ion-social-youtube-outline:before,
.ion-soup-can:before,
.ion-soup-can-outline:before,
.ion-speakerphone:before,
.ion-speedometer:before,
.ion-spoon:before,
.ion-star:before,
.ion-stats-bars:before,
.ion-steam:before,
.ion-stop:before,
.ion-thermometer:before,
.ion-thumbsdown:before,
.ion-thumbsup:before,
.ion-toggle:before,
.ion-toggle-filled:before,
.ion-transgender:before,
.ion-trash-a:before,
.ion-trash-b:before,
.ion-trophy:before,
.ion-tshirt:before,
.ion-tshirt-outline:before,
.ion-umbrella:before,
.ion-university:before,
.ion-unlocked:before,
.ion-upload:before,
.ion-usb:before,
.ion-videocamera:before,
.ion-volume-high:before,
.ion-volume-low:before,
.ion-volume-medium:before,
.ion-volume-mute:before,
.ion-wand:before,
.ion-waterdrop:before,
.ion-wifi:before,
.ion-wineglass:before,
.ion-woman:before,
.ion-wrench:before,
.ion-xbox:before {
display: inline-block;
font-family: "Ionicons";
speak: none;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
text-rendering: auto;
line-height: 1;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; }
.ion-alert:before {
content: ""; }
.ion-alert-circled:before {
content: ""; }
.ion-android-add:before {
content: ""; }
.ion-android-add-circle:before {
content: ""; }
.ion-android-alarm-clock:before {
content: ""; }
.ion-android-alert:before {
content: ""; }
.ion-android-apps:before {
content: ""; }
.ion-android-archive:before {
content: ""; }
.ion-android-arrow-back:before {
content: ""; }
.ion-android-arrow-down:before {
content: ""; }
.ion-android-arrow-dropdown:before {
content: ""; }
.ion-android-arrow-dropdown-circle:before {
content: ""; }
.ion-android-arrow-dropleft:before {
content: ""; }
.ion-android-arrow-dropleft-circle:before {
content: ""; }
.ion-android-arrow-dropright:before {
content: ""; }
.ion-android-arrow-dropright-circle:before {
content: ""; }
.ion-android-arrow-dropup:before {
content: ""; }
.ion-android-arrow-dropup-circle:before {
content: ""; }
.ion-android-arrow-forward:before {
content: ""; }
.ion-android-arrow-up:before {
content: ""; }
.ion-android-attach:before {
content: ""; }
.ion-android-bar:before {
content: ""; }
.ion-android-bicycle:before {
content: ""; }
.ion-android-boat:before {
content: ""; }
.ion-android-bookmark:before {
content: ""; }
.ion-android-bulb:before {
content: ""; }
.ion-android-bus:before {
content: ""; }
.ion-android-calendar:before {
content: ""; }
.ion-android-call:before {
content: ""; }
.ion-android-camera:before {
content: ""; }
.ion-android-cancel:before {
content: ""; }
.ion-android-car:before {
content: ""; }
.ion-android-cart:before {
content: ""; }
.ion-android-chat:before {
content: ""; }
.ion-android-checkbox:before {
content: ""; }
.ion-android-checkbox-blank:before {
content: ""; }
.ion-android-checkbox-outline:before {
content: ""; }
.ion-android-checkbox-outline-blank:before {
content: ""; }
.ion-android-checkmark-circle:before {
content: ""; }
.ion-android-clipboard:before {
content: ""; }
.ion-android-close:before {
content: ""; }
.ion-android-cloud:before {
content: ""; }
.ion-android-cloud-circle:before {
content: ""; }
.ion-android-cloud-done:before {
content: ""; }
.ion-android-cloud-outline:before {
content: ""; }
.ion-android-color-palette:before {
content: ""; }
.ion-android-compass:before {
content: ""; }
.ion-android-contact:before {
content: ""; }
.ion-android-contacts:before {
content: ""; }
.ion-android-contract:before {
content: ""; }
.ion-android-create:before {
content: ""; }
.ion-android-delete:before {
content: ""; }
.ion-android-desktop:before {
content: ""; }
.ion-android-document:before {
content: ""; }
.ion-android-done:before {
content: ""; }
.ion-android-done-all:before {
content: ""; }
.ion-android-download:before {
content: ""; }
.ion-android-drafts:before {
content: ""; }
.ion-android-exit:before {
content: ""; }
.ion-android-expand:before {
content: ""; }
.ion-android-favorite:before {
content: ""; }
.ion-android-favorite-outline:before {
content: ""; }
.ion-android-film:before {
content: ""; }
.ion-android-folder:before {
content: ""; }
.ion-android-folder-open:before {
content: ""; }
.ion-android-funnel:before {
content: ""; }
.ion-android-globe:before {
content: ""; }
.ion-android-hand:before {
content: ""; }
.ion-android-hangout:before {
content: ""; }
.ion-android-happy:before {
content: ""; }
.ion-android-home:before {
content: ""; }
.ion-android-image:before {
content: ""; }
.ion-android-laptop:before {
content: ""; }
.ion-android-list:before {
content: ""; }
.ion-android-locate:before, .platform-android .tatami-location:before {
content: ""; }
.ion-android-lock:before {
content: ""; }
.ion-android-mail:before {
content: ""; }
.ion-android-map:before {
content: ""; }
.ion-android-menu:before {
content: ""; }
.ion-android-microphone:before {
content: ""; }
.ion-android-microphone-off:before {
content: ""; }
.ion-android-more-horizontal:before {
content: ""; }
.ion-android-more-vertical:before {
content: ""; }
.ion-android-navigate:before {
content: ""; }
.ion-android-notifications:before {
content: ""; }
.ion-android-notifications-none:before {
content: ""; }
.ion-android-notifications-off:before {
content: ""; }
.ion-android-open:before {
content: ""; }
.ion-android-options:before {
content: ""; }
.ion-android-people:before {
content: ""; }
.ion-android-person:before {
content: ""; }
.ion-android-person-add:before {
content: ""; }
.ion-android-phone-landscape:before {
content: ""; }
.ion-android-phone-portrait:before {
content: ""; }
.ion-android-pin:before {
content: ""; }
.ion-android-plane:before {
content: ""; }
.ion-android-playstore:before {
content: ""; }
.ion-android-print:before {
content: ""; }
.ion-android-radio-button-off:before {
content: ""; }
.ion-android-radio-button-on:before {
content: ""; }
.ion-android-refresh:before {
content: ""; }
.ion-android-remove:before {
content: ""; }
.ion-android-remove-circle:before {
content: ""; }
.ion-android-restaurant:before {
content: ""; }
.ion-android-sad:before {
content: ""; }
.ion-android-search:before {
content: ""; }
.ion-android-send:before {
content: ""; }
.ion-android-settings:before {
content: ""; }
.ion-android-share:before {
content: ""; }
.ion-android-share-alt:before {
content: ""; }
.ion-android-star:before {
content: ""; }
.ion-android-star-half:before {
content: ""; }
.ion-android-star-outline:before {
content: ""; }
.ion-android-stopwatch:before {
content: ""; }
.ion-android-subway:before {
content: ""; }
.ion-android-sunny:before {
content: ""; }
.ion-android-sync:before {
content: ""; }
.ion-android-textsms:before {
content: ""; }
.ion-android-time:before {
content: ""; }
.ion-android-train:before {
content: ""; }
.ion-android-unlock:before {
content: ""; }
.ion-android-upload:before {
content: ""; }
.ion-android-volume-down:before {
content: ""; }
.ion-android-volume-mute:before {
content: ""; }
.ion-android-volume-off:before {
content: ""; }
.ion-android-volume-up:before {
content: ""; }
.ion-android-walk:before {
content: ""; }
.ion-android-warning:before {
content: ""; }
.ion-android-watch:before {
content: ""; }
.ion-android-wifi:before {
content: ""; }
.ion-aperture:before {
content: ""; }
.ion-archive:before {
content: ""; }
.ion-arrow-down-a:before {
content: ""; }
.ion-arrow-down-b:before {
content: ""; }
.ion-arrow-down-c:before {
content: ""; }
.ion-arrow-expand:before {
content: ""; }
.ion-arrow-graph-down-left:before {
content: ""; }
.ion-arrow-graph-down-right:before {
content: ""; }
.ion-arrow-graph-up-left:before {
content: ""; }
.ion-arrow-graph-up-right:before {
content: ""; }
.ion-arrow-left-a:before {
content: ""; }
.ion-arrow-left-b:before {
content: ""; }
.ion-arrow-left-c:before {
content: ""; }
.ion-arrow-move:before {
content: ""; }
.ion-arrow-resize:before {
content: ""; }
.ion-arrow-return-left:before {
content: ""; }
.ion-arrow-return-right:before {
content: ""; }
.ion-arrow-right-a:before {
content: ""; }
.ion-arrow-right-b:before {
content: ""; }
.ion-arrow-right-c:before {
content: ""; }
.ion-arrow-shrink:before {
content: ""; }
.ion-arrow-swap:before {
content: ""; }
.ion-arrow-up-a:before {
content: ""; }
.ion-arrow-up-b:before {
content: ""; }
.ion-arrow-up-c:before {
content: ""; }
.ion-asterisk:before {
content: ""; }
.ion-at:before {
content: ""; }
.ion-backspace:before {
content: ""; }
.ion-backspace-outline:before {
content: ""; }
.ion-bag:before {
content: ""; }
.ion-battery-charging:before {
content: ""; }
.ion-battery-empty:before {
content: ""; }
.ion-battery-full:before {
content: ""; }
.ion-battery-half:before {
content: ""; }
.ion-battery-low:before {
content: ""; }
.ion-beaker:before {
content: ""; }
.ion-beer:before {
content: ""; }
.ion-bluetooth:before {
content: ""; }
.ion-bonfire:before {
content: ""; }
.ion-bookmark:before {
content: ""; }
.ion-bowtie:before {
content: ""; }
.ion-briefcase:before {
content: ""; }
.ion-bug:before {
content: ""; }
.ion-calculator:before {
content: ""; }
.ion-calendar:before {
content: ""; }
.ion-camera:before {
content: ""; }
.ion-card:before {
content: ""; }
.ion-cash:before {
content: ""; }
.ion-chatbox:before {
content: ""; }
.ion-chatbox-working:before {
content: ""; }
.ion-chatboxes:before {
content: ""; }
.ion-chatbubble:before {
content: ""; }
.ion-chatbubble-working:before {
content: ""; }
.ion-chatbubbles:before {
content: ""; }
.ion-checkmark:before {
content: ""; }
.ion-checkmark-circled:before {
content: ""; }
.ion-checkmark-round:before {
content: ""; }
.ion-chevron-down:before {
content: ""; }
.ion-chevron-left:before {
content: ""; }
.ion-chevron-right:before {
content: ""; }
.ion-chevron-up:before {
content: ""; }
.ion-clipboard:before {
content: ""; }
.ion-clock:before {
content: ""; }
.ion-close:before {
content: ""; }
.ion-close-circled:before {
content: ""; }
.ion-close-round:before {
content: ""; }
.ion-closed-captioning:before {
content: ""; }
.ion-cloud:before {
content: ""; }
.ion-code:before {
content: ""; }
.ion-code-download:before {
content: ""; }
.ion-code-working:before {
content: ""; }
.ion-coffee:before {
content: ""; }
.ion-compass:before {
content: ""; }
.ion-compose:before {
content: ""; }
.ion-connection-bars:before {
content: ""; }
.ion-contrast:before {
content: ""; }
.ion-crop:before {
content: ""; }
.ion-cube:before {
content: ""; }
.ion-disc:before {
content: ""; }
.ion-document:before {
content: ""; }
.ion-document-text:before {
content: ""; }
.ion-drag:before {
content: ""; }
.ion-earth:before {
content: ""; }
.ion-easel:before {
content: ""; }
.ion-edit:before {
content: ""; }
.ion-egg:before {
content: ""; }
.ion-eject:before {
content: ""; }
.ion-email:before {
content: ""; }
.ion-email-unread:before {
content: ""; }
.ion-erlenmeyer-flask:before {
content: ""; }
.ion-erlenmeyer-flask-bubbles:before {
content: ""; }
.ion-eye:before {
content: ""; }
.ion-eye-disabled:before {
content: ""; }
.ion-female:before {
content: ""; }
.ion-filing:before {
content: ""; }
.ion-film-marker:before {
content: ""; }
.ion-fireball:before {
content: ""; }
.ion-flag:before {
content: ""; }
.ion-flame:before {
content: ""; }
.ion-flash:before {
content: ""; }
.ion-flash-off:before {
content: ""; }
.ion-folder:before {
content: ""; }
.ion-fork:before {
content: ""; }
.ion-fork-repo:before {
content: ""; }
.ion-forward:before {
content: ""; }
.ion-funnel:before {
content: ""; }
.ion-gear-a:before {
content: ""; }
.ion-gear-b:before {
content: ""; }
.ion-grid:before {
content: ""; }
.ion-hammer:before {
content: ""; }
.ion-happy:before {
content: ""; }
.ion-happy-outline:before {
content: ""; }
.ion-headphone:before {
content: ""; }
.ion-heart:before {
content: ""; }
.ion-heart-broken:before {
content: ""; }
.ion-help:before {
content: ""; }
.ion-help-buoy:before {
content: ""; }
.ion-help-circled:before {
content: ""; }
.ion-home:before {
content: ""; }
.ion-icecream:before {
content: ""; }
.ion-image:before {
content: ""; }
.ion-images:before {
content: ""; }
.ion-information:before {
content: ""; }
.ion-information-circled:before {
content: ""; }
.ion-ionic:before {
content: ""; }
.ion-ios-alarm:before {
content: ""; }
.ion-ios-alarm-outline:before {
content: ""; }
.ion-ios-albums:before {
content: ""; }
.ion-ios-albums-outline:before {
content: ""; }
.ion-ios-americanfootball:before {
content: ""; }
.ion-ios-americanfootball-outline:before {
content: ""; }
.ion-ios-analytics:before {
content: ""; }
.ion-ios-analytics-outline:before {
content: ""; }
.ion-ios-arrow-back:before {
content: ""; }
.ion-ios-arrow-down:before {
content: ""; }
.ion-ios-arrow-forward:before {
content: ""; }
.ion-ios-arrow-left:before {
content: ""; }
.ion-ios-arrow-right:before {
content: ""; }
.ion-ios-arrow-thin-down:before {
content: ""; }
.ion-ios-arrow-thin-left:before {
content: ""; }
.ion-ios-arrow-thin-right:before {
content: ""; }
.ion-ios-arrow-thin-up:before {
content: ""; }
.ion-ios-arrow-up:before {
content: ""; }
.ion-ios-at:before {
content: ""; }
.ion-ios-at-outline:before {
content: ""; }
.ion-ios-barcode:before {
content: ""; }
.ion-ios-barcode-outline:before {
content: ""; }
.ion-ios-baseball:before {
content: ""; }
.ion-ios-baseball-outline:before {
content: ""; }
.ion-ios-basketball:before {
content: ""; }
.ion-ios-basketball-outline:before {
content: ""; }
.ion-ios-bell:before {
content: ""; }
.ion-ios-bell-outline:before {
content: ""; }
.ion-ios-body:before {
content: ""; }
.ion-ios-body-outline:before {
content: ""; }
.ion-ios-bolt:before {
content: ""; }
.ion-ios-bolt-outline:before {
content: ""; }
.ion-ios-book:before {
content: ""; }
.ion-ios-book-outline:before {
content: ""; }
.ion-ios-bookmarks:before {
content: ""; }
.ion-ios-bookmarks-outline:before {
content: ""; }
.ion-ios-box:before {
content: ""; }
.ion-ios-box-outline:before {
content: ""; }
.ion-ios-briefcase:before {
content: ""; }
.ion-ios-briefcase-outline:before {
content: ""; }
.ion-ios-browsers:before {
content: ""; }
.ion-ios-browsers-outline:before {
content: ""; }
.ion-ios-calculator:before {
content: ""; }
.ion-ios-calculator-outline:before {
content: ""; }
.ion-ios-calendar:before {
content: ""; }
.ion-ios-calendar-outline:before {
content: ""; }
.ion-ios-camera:before {
content: ""; }
.ion-ios-camera-outline:before {
content: ""; }
.ion-ios-cart:before {
content: ""; }
.ion-ios-cart-outline:before {
content: ""; }
.ion-ios-chatboxes:before {
content: ""; }
.ion-ios-chatboxes-outline:before {
content: ""; }
.ion-ios-chatbubble:before {
content: ""; }
.ion-ios-chatbubble-outline:before {
content: ""; }
.ion-ios-checkmark:before {
content: ""; }
.ion-ios-checkmark-empty:before {
content: ""; }
.ion-ios-checkmark-outline:before {
content: ""; }
.ion-ios-circle-filled:before {
content: ""; }
.ion-ios-circle-outline:before {
content: ""; }
.ion-ios-clock:before {
content: ""; }
.ion-ios-clock-outline:before {
content: ""; }
.ion-ios-close:before {
content: ""; }
.ion-ios-close-empty:before {
content: ""; }
.ion-ios-close-outline:before {
content: ""; }
.ion-ios-cloud:before {
content: ""; }
.ion-ios-cloud-download:before {
content: ""; }
.ion-ios-cloud-download-outline:before {
content: ""; }
.ion-ios-cloud-outline:before {
content: ""; }
.ion-ios-cloud-upload:before {
content: ""; }
.ion-ios-cloud-upload-outline:before {
content: ""; }
.ion-ios-cloudy:before {
content: ""; }
.ion-ios-cloudy-night:before {
content: ""; }
.ion-ios-cloudy-night-outline:before {
content: ""; }
.ion-ios-cloudy-outline:before {
content: ""; }
.ion-ios-cog:before {
content: ""; }
.ion-ios-cog-outline:before {
content: ""; }
.ion-ios-color-filter:before {
content: ""; }
.ion-ios-color-filter-outline:before {
content: ""; }
.ion-ios-color-wand:before {
content: ""; }
.ion-ios-color-wand-outline:before {
content: ""; }
.ion-ios-compose:before {
content: ""; }
.ion-ios-compose-outline:before {
content: ""; }
.ion-ios-contact:before {
content: ""; }
.ion-ios-contact-outline:before {
content: ""; }
.ion-ios-copy:before {
content: ""; }
.ion-ios-copy-outline:before {
content: ""; }
.ion-ios-crop:before {
content: ""; }
.ion-ios-crop-strong:before {
content: ""; }
.ion-ios-download:before {
content: ""; }
.ion-ios-download-outline:before {
content: ""; }
.ion-ios-drag:before {
content: ""; }
.ion-ios-email:before {
content: ""; }
.ion-ios-email-outline:before {
content: ""; }
.ion-ios-eye:before {
content: ""; }
.ion-ios-eye-outline:before {
content: ""; }
.ion-ios-fastforward:before {
content: ""; }
.ion-ios-fastforward-outline:before {
content: ""; }
.ion-ios-filing:before {
content: ""; }
.ion-ios-filing-outline:before {
content: ""; }
.ion-ios-film:before {
content: ""; }
.ion-ios-film-outline:before {
content: ""; }
.ion-ios-flag:before {
content: ""; }
.ion-ios-flag-outline:before {
content: ""; }
.ion-ios-flame:before {
content: ""; }
.ion-ios-flame-outline:before {
content: ""; }
.ion-ios-flask:before {
content: ""; }
.ion-ios-flask-outline:before {
content: ""; }
.ion-ios-flower:before {
content: ""; }
.ion-ios-flower-outline:before {
content: ""; }
.ion-ios-folder:before {
content: ""; }
.ion-ios-folder-outline:before {
content: ""; }
.ion-ios-football:before {
content: ""; }
.ion-ios-football-outline:before {
content: ""; }
.ion-ios-game-controller-a:before {
content: ""; }
.ion-ios-game-controller-a-outline:before {
content: ""; }
.ion-ios-game-controller-b:before {
content: ""; }
.ion-ios-game-controller-b-outline:before {
content: ""; }
.ion-ios-gear:before {
content: ""; }
.ion-ios-gear-outline:before {
content: ""; }
.ion-ios-glasses:before {
content: ""; }
.ion-ios-glasses-outline:before {
content: ""; }
.ion-ios-grid-view:before {
content: ""; }
.ion-ios-grid-view-outline:before {
content: ""; }
.ion-ios-heart:before {
content: ""; }
.ion-ios-heart-outline:before {
content: ""; }
.ion-ios-help:before {
content: ""; }
.ion-ios-help-empty:before {
content: ""; }
.ion-ios-help-outline:before {
content: ""; }
.ion-ios-home:before {
content: ""; }
.ion-ios-home-outline:before {
content: ""; }
.ion-ios-infinite:before {
content: ""; }
.ion-ios-infinite-outline:before {
content: ""; }
.ion-ios-information:before {
content: ""; }
.ion-ios-information-empty:before {
content: ""; }
.ion-ios-information-outline:before {
content: ""; }
.ion-ios-ionic-outline:before {
content: ""; }
.ion-ios-keypad:before {
content: ""; }
.ion-ios-keypad-outline:before {
content: ""; }
.ion-ios-lightbulb:before {
content: ""; }
.ion-ios-lightbulb-outline:before {
content: ""; }
.ion-ios-list:before {
content: ""; }
.ion-ios-list-outline:before {
content: ""; }
.ion-ios-location:before, .platform-ios .tatami-location:before {
content: ""; }
.ion-ios-location-outline:before {
content: ""; }
.ion-ios-locked:before {
content: ""; }
.ion-ios-locked-outline:before {
content: ""; }
.ion-ios-loop:before {
content: ""; }
.ion-ios-loop-strong:before {
content: ""; }
.ion-ios-medical:before {
content: ""; }
.ion-ios-medical-outline:before {
content: ""; }
.ion-ios-medkit:before {
content: ""; }
.ion-ios-medkit-outline:before {
content: ""; }
.ion-ios-mic:before {
content: ""; }
.ion-ios-mic-off:before {
content: ""; }
.ion-ios-mic-outline:before {
content: ""; }
.ion-ios-minus:before {
content: ""; }
.ion-ios-minus-empty:before {
content: ""; }
.ion-ios-minus-outline:before {
content: ""; }
.ion-ios-monitor:before {
content: ""; }
.ion-ios-monitor-outline:before {
content: ""; }
.ion-ios-moon:before {
content: ""; }
.ion-ios-moon-outline:before {
content: ""; }
.ion-ios-more:before {
content: ""; }
.ion-ios-more-outline:before {
content: ""; }
.ion-ios-musical-note:before {
content: ""; }
.ion-ios-musical-notes:before {
content: ""; }
.ion-ios-navigate:before {
content: ""; }
.ion-ios-navigate-outline:before {
content: ""; }
.ion-ios-nutrition:before {
content: ""; }
.ion-ios-nutrition-outline:before {
content: ""; }
.ion-ios-paper:before {
content: ""; }
.ion-ios-paper-outline:before {
content: ""; }
.ion-ios-paperplane:before {
content: ""; }
.ion-ios-paperplane-outline:before {
content: ""; }
.ion-ios-partlysunny:before {
content: ""; }
.ion-ios-partlysunny-outline:before {
content: ""; }
.ion-ios-pause:before {
content: ""; }
.ion-ios-pause-outline:before {
content: ""; }
.ion-ios-paw:before {
content: ""; }
.ion-ios-paw-outline:before {
content: ""; }
.ion-ios-people:before {
content: ""; }
.ion-ios-people-outline:before {
content: ""; }
.ion-ios-person:before {
content: ""; }
.ion-ios-person-outline:before {
content: ""; }
.ion-ios-personadd:before {
content: ""; }
.ion-ios-personadd-outline:before {
content: ""; }
.ion-ios-photos:before {
content: ""; }
.ion-ios-photos-outline:before {
content: ""; }
.ion-ios-pie:before {
content: ""; }
.ion-ios-pie-outline:before {
content: ""; }
.ion-ios-pint:before {
content: ""; }
.ion-ios-pint-outline:before {
content: ""; }
.ion-ios-play:before {
content: ""; }
.ion-ios-play-outline:before {
content: ""; }
.ion-ios-plus:before {
content: ""; }
.ion-ios-plus-empty:before {
content: ""; }
.ion-ios-plus-outline:before {
content: ""; }
.ion-ios-pricetag:before {
content: ""; }
.ion-ios-pricetag-outline:before {
content: ""; }
.ion-ios-pricetags:before {
content: ""; }
.ion-ios-pricetags-outline:before {
content: ""; }
.ion-ios-printer:before {
content: ""; }
.ion-ios-printer-outline:before {
content: ""; }
.ion-ios-pulse:before {
content: ""; }
.ion-ios-pulse-strong:before {
content: ""; }
.ion-ios-rainy:before {
content: ""; }
.ion-ios-rainy-outline:before {
content: ""; }
.ion-ios-recording:before {
content: ""; }
.ion-ios-recording-outline:before {
content: ""; }
.ion-ios-redo:before {
content: ""; }
.ion-ios-redo-outline:before {
content: ""; }
.ion-ios-refresh:before {
content: ""; }
.ion-ios-refresh-empty:before {
content: ""; }
.ion-ios-refresh-outline:before {
content: ""; }
.ion-ios-reload:before {
content: ""; }
.ion-ios-reverse-camera:before {
content: ""; }
.ion-ios-reverse-camera-outline:before {
content: ""; }
.ion-ios-rewind:before {
content: ""; }
.ion-ios-rewind-outline:before {
content: ""; }
.ion-ios-rose:before {
content: ""; }
.ion-ios-rose-outline:before {
content: ""; }
.ion-ios-search:before {
content: ""; }
.ion-ios-search-strong:before {
content: ""; }
.ion-ios-settings:before {
content: ""; }
.ion-ios-settings-strong:before {
content: ""; }
.ion-ios-shuffle:before {
content: ""; }
.ion-ios-shuffle-strong:before {
content: ""; }
.ion-ios-skipbackward:before {
content: ""; }
.ion-ios-skipbackward-outline:before {
content: ""; }
.ion-ios-skipforward:before {
content: ""; }
.ion-ios-skipforward-outline:before {
content: ""; }
.ion-ios-snowy:before {
content: ""; }
.ion-ios-speedometer:before {
content: ""; }
.ion-ios-speedometer-outline:before {
content: ""; }
.ion-ios-star:before {
content: ""; }
.ion-ios-star-half:before {
content: ""; }
.ion-ios-star-outline:before {
content: ""; }
.ion-ios-stopwatch:before {
content: ""; }
.ion-ios-stopwatch-outline:before {
content: ""; }
.ion-ios-sunny:before {
content: ""; }
.ion-ios-sunny-outline:before {
content: ""; }
.ion-ios-telephone:before {
content: ""; }
.ion-ios-telephone-outline:before {
content: ""; }
.ion-ios-tennisball:before {
content: ""; }
.ion-ios-tennisball-outline:before {
content: ""; }
.ion-ios-thunderstorm:before {
content: ""; }
.ion-ios-thunderstorm-outline:before {
content: ""; }
.ion-ios-time:before {
content: ""; }
.ion-ios-time-outline:before {
content: ""; }
.ion-ios-timer:before {
content: ""; }
.ion-ios-timer-outline:before {
content: ""; }
.ion-ios-toggle:before {
content: ""; }
.ion-ios-toggle-outline:before {
content: ""; }
.ion-ios-trash:before {
content: ""; }
.ion-ios-trash-outline:before {
content: ""; }
.ion-ios-undo:before {
content: ""; }
.ion-ios-undo-outline:before {
content: ""; }
.ion-ios-unlocked:before {
content: ""; }
.ion-ios-unlocked-outline:before {
content: ""; }
.ion-ios-upload:before {
content: ""; }
.ion-ios-upload-outline:before {
content: ""; }
.ion-ios-videocam:before {
content: ""; }
.ion-ios-videocam-outline:before {
content: ""; }
.ion-ios-volume-high:before {
content: ""; }
.ion-ios-volume-low:before {
content: ""; }
.ion-ios-wineglass:before {
content: ""; }
.ion-ios-wineglass-outline:before {
content: ""; }
.ion-ios-world:before {
content: ""; }
.ion-ios-world-outline:before {
content: ""; }
.ion-ipad:before {
content: ""; }
.ion-iphone:before {
content: ""; }
.ion-ipod:before {
content: ""; }
.ion-jet:before {
content: ""; }
.ion-key:before {
content: ""; }
.ion-knife:before {
content: ""; }
.ion-laptop:before {
content: ""; }
.ion-leaf:before {
content: ""; }
.ion-levels:before {
content: ""; }
.ion-lightbulb:before {
content: ""; }
.ion-link:before {
content: ""; }
.ion-load-a:before {
content: ""; }
.ion-load-b:before {
content: ""; }
.ion-load-c:before {
content: ""; }
.ion-load-d:before {
content: ""; }
.ion-location:before {
content: ""; }
.ion-lock-combination:before {
content: ""; }
.ion-locked:before {
content: ""; }
.ion-log-in:before {
content: ""; }
.ion-log-out:before {
content: ""; }
.ion-loop:before {
content: ""; }
.ion-magnet:before {
content: ""; }
.ion-male:before {
content: ""; }
.ion-man:before {
content: ""; }
.ion-map:before {
content: ""; }
.ion-medkit:before {
content: ""; }
.ion-merge:before {
content: ""; }
.ion-mic-a:before {
content: ""; }
.ion-mic-b:before {
content: ""; }
.ion-mic-c:before {
content: ""; }
.ion-minus:before {
content: ""; }
.ion-minus-circled:before {
content: ""; }
.ion-minus-round:before {
content: ""; }
.ion-model-s:before {
content: ""; }
.ion-monitor:before {
content: ""; }
.ion-more:before {
content: ""; }
.ion-mouse:before {
content: ""; }
.ion-music-note:before {
content: ""; }
.ion-navicon:before {
content: ""; }
.ion-navicon-round:before {
content: ""; }
.ion-navigate:before {
content: ""; }
.ion-network:before {
content: ""; }
.ion-no-smoking:before {
content: ""; }
.ion-nuclear:before {
content: ""; }
.ion-outlet:before {
content: ""; }
.ion-paintbrush:before {
content: ""; }
.ion-paintbucket:before {
content: ""; }
.ion-paper-airplane:before {
content: ""; }
.ion-paperclip:before {
content: ""; }
.ion-pause:before {
content: ""; }
.ion-person:before {
content: ""; }
.ion-person-add:before {
content: ""; }
.ion-person-stalker:before {
content: ""; }
.ion-pie-graph:before {
content: ""; }
.ion-pin:before {
content: ""; }
.ion-pinpoint:before {
content: ""; }
.ion-pizza:before {
content: ""; }
.ion-plane:before {
content: ""; }
.ion-planet:before {
content: ""; }
.ion-play:before {
content: ""; }
.ion-playstation:before {
content: ""; }
.ion-plus:before {
content: ""; }
.ion-plus-circled:before {
content: ""; }
.ion-plus-round:before {
content: ""; }
.ion-podium:before {
content: ""; }
.ion-pound:before {
content: ""; }
.ion-power:before {
content: ""; }
.ion-pricetag:before {
content: ""; }
.ion-pricetags:before {
content: ""; }
.ion-printer:before {
content: ""; }
.ion-pull-request:before {
content: ""; }
.ion-qr-scanner:before {
content: ""; }
.ion-quote:before {
content: ""; }
.ion-radio-waves:before {
content: ""; }
.ion-record:before {
content: ""; }
.ion-refresh:before {
content: ""; }
.ion-reply:before {
content: ""; }
.ion-reply-all:before {
content: ""; }
.ion-ribbon-a:before {
content: ""; }
.ion-ribbon-b:before {
content: ""; }
.ion-sad:before {
content: ""; }
.ion-sad-outline:before {
content: ""; }
.ion-scissors:before {
content: ""; }
.ion-search:before {
content: ""; }
.ion-settings:before {
content: ""; }
.ion-share:before {
content: ""; }
.ion-shuffle:before {
content: ""; }
.ion-skip-backward:before {
content: ""; }
.ion-skip-forward:before {
content: ""; }
.ion-social-android:before {
content: ""; }
.ion-social-android-outline:before {
content: ""; }
.ion-social-angular:before {
content: ""; }
.ion-social-angular-outline:before {
content: ""; }
.ion-social-apple:before {
content: ""; }
.ion-social-apple-outline:before {
content: ""; }
.ion-social-bitcoin:before {
content: ""; }
.ion-social-bitcoin-outline:before {
content: ""; }
.ion-social-buffer:before {
content: ""; }
.ion-social-buffer-outline:before {
content: ""; }
.ion-social-chrome:before {
content: ""; }
.ion-social-chrome-outline:before {
content: ""; }
.ion-social-codepen:before {
content: ""; }
.ion-social-codepen-outline:before {
content: ""; }
.ion-social-css3:before {
content: ""; }
.ion-social-css3-outline:before {
content: ""; }
.ion-social-designernews:before {
content: ""; }
.ion-social-designernews-outline:before {
content: ""; }
.ion-social-dribbble:before {
content: ""; }
.ion-social-dribbble-outline:before {
content: ""; }
.ion-social-dropbox:before {
content: ""; }
.ion-social-dropbox-outline:before {
content: ""; }
.ion-social-euro:before {
content: ""; }
.ion-social-euro-outline:before {
content: ""; }
.ion-social-facebook:before {
content: ""; }
.ion-social-facebook-outline:before {
content: ""; }
.ion-social-foursquare:before {
content: ""; }
.ion-social-foursquare-outline:before {
content: ""; }
.ion-social-freebsd-devil:before {
content: ""; }
.ion-social-github:before {
content: ""; }
.ion-social-github-outline:before {
content: ""; }
.ion-social-google:before {
content: ""; }
.ion-social-google-outline:before {
content: ""; }
.ion-social-googleplus:before {
content: ""; }
.ion-social-googleplus-outline:before {
content: ""; }
.ion-social-hackernews:before {
content: ""; }
.ion-social-hackernews-outline:before {
content: ""; }
.ion-social-html5:before {
content: ""; }
.ion-social-html5-outline:before {
content: ""; }
.ion-social-instagram:before {
content: ""; }
.ion-social-instagram-outline:before {
content: ""; }
.ion-social-javascript:before {
content: ""; }
.ion-social-javascript-outline:before {
content: ""; }
.ion-social-linkedin:before {
content: ""; }
.ion-social-linkedin-outline:before {
content: ""; }
.ion-social-markdown:before {
content: ""; }
.ion-social-nodejs:before {
content: ""; }
.ion-social-octocat:before {
content: ""; }
.ion-social-pinterest:before {
content: ""; }
.ion-social-pinterest-outline:before {
content: ""; }
.ion-social-python:before {
content: ""; }
.ion-social-reddit:before {
content: ""; }
.ion-social-reddit-outline:before {
content: ""; }
.ion-social-rss:before {
content: ""; }
.ion-social-rss-outline:before {
content: ""; }
.ion-social-sass:before {
content: ""; }
.ion-social-skype:before {
content: ""; }
.ion-social-skype-outline:before {
content: ""; }
.ion-social-snapchat:before {
content: ""; }
.ion-social-snapchat-outline:before {
content: ""; }
.ion-social-tumblr:before {
content: ""; }
.ion-social-tumblr-outline:before {
content: ""; }
.ion-social-tux:before {
content: ""; }
.ion-social-twitch:before {
content: ""; }
.ion-social-twitch-outline:before {
content: ""; }
.ion-social-twitter:before {
content: ""; }
.ion-social-twitter-outline:before {
content: ""; }
.ion-social-usd:before {
content: ""; }
.ion-social-usd-outline:before {
content: ""; }
.ion-social-vimeo:before {
content: ""; }
.ion-social-vimeo-outline:before {
content: ""; }
.ion-social-whatsapp:before {
content: ""; }
.ion-social-whatsapp-outline:before {
content: ""; }
.ion-social-windows:before {
content: ""; }
.ion-social-windows-outline:before {
content: ""; }
.ion-social-wordpress:before {
content: ""; }
.ion-social-wordpress-outline:before {
content: ""; }
.ion-social-yahoo:before {
content: ""; }
.ion-social-yahoo-outline:before {
content: ""; }
.ion-social-yen:before {
content: ""; }
.ion-social-yen-outline:before {
content: ""; }
.ion-social-youtube:before {
content: ""; }
.ion-social-youtube-outline:before {
content: ""; }
.ion-soup-can:before {
content: ""; }
.ion-soup-can-outline:before {
content: ""; }
.ion-speakerphone:before {
content: ""; }
.ion-speedometer:before {
content: ""; }
.ion-spoon:before {
content: ""; }
.ion-star:before {
content: ""; }
.ion-stats-bars:before {
content: ""; }
.ion-steam:before {
content: ""; }
.ion-stop:before {
content: ""; }
.ion-thermometer:before {
content: ""; }
.ion-thumbsdown:before {
content: ""; }
.ion-thumbsup:before {
content: ""; }
.ion-toggle:before {
content: ""; }
.ion-toggle-filled:before {
content: ""; }
.ion-transgender:before {
content: ""; }
.ion-trash-a:before {
content: ""; }
.ion-trash-b:before {
content: ""; }
.ion-trophy:before {
content: ""; }
.ion-tshirt:before {
content: ""; }
.ion-tshirt-outline:before {
content: ""; }
.ion-umbrella:before {
content: ""; }
.ion-university:before {
content: ""; }
.ion-unlocked:before {
content: ""; }
.ion-upload:before {
content: ""; }
.ion-usb:before {
content: ""; }
.ion-videocamera:before {
content: ""; }
.ion-volume-high:before {
content: ""; }
.ion-volume-low:before {
content: ""; }
.ion-volume-medium:before {
content: ""; }
.ion-volume-mute:before {
content: ""; }
.ion-wand:before {
content: ""; }
.ion-waterdrop:before {
content: ""; }
.ion-wifi:before {
content: ""; }
.ion-wineglass:before {
content: ""; }
.ion-woman:before {
content: ""; }
.ion-wrench:before {
content: ""; }
.ion-xbox:before {
content: ""; }
/**
* Resets
* --------------------------------------------------
* Adapted from normalize.css and some reset.css. We don't care even one
* bit about old IE, so we don't need any hacks for that in here.
*
* There are probably other things we could remove here, as well.
*
* normalize.css v2.1.2 | MIT License | git.io/normalize
* Eric Meyer's Reset CSS v2.0 (http://meyerweb.com/eric/tools/css/reset/)
* http://cssreset.com
*/
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, i, u, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed, fieldset,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
vertical-align: baseline;
font: inherit;
font-size: 100%; }
ol, ul {
list-style: none; }
blockquote, q {
quotes: none; }
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none; }
/**
* Prevent modern browsers from displaying `audio` without controls.
* Remove excess height in iOS 5 devices.
*/
audio:not([controls]) {
display: none;
height: 0; }
/**
* Hide the `template` element in IE, Safari, and Firefox < 22.
*/
[hidden],
template {
display: none; }
script {
display: none !important; }
/* ==========================================================================
Base
========================================================================== */
/**
* 1. Set default font family to sans-serif.
* 2. Prevent iOS text size adjust after orientation change, without disabling
* user zoom.
*/
html {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
font-family: sans-serif;
/* 1 */
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
/* 2 */
-webkit-text-size-adjust: 100%;
/* 2 */ }
/**
* Remove default margin.
*/
body {
margin: 0;
line-height: 1; }
/**
* Remove default outlines.
*/
a,
button,
:focus,
a:focus,
button:focus,
a:active,
a:hover {
outline: 0; }
/* *
* Remove tap highlight color
*/
a {
-webkit-user-drag: none;
-webkit-tap-highlight-color: transparent;
-webkit-tap-highlight-color: transparent; }
a[href]:hover {
cursor: pointer; }
/* ==========================================================================
Typography
========================================================================== */
/**
* Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome.
*/
b,
strong {
font-weight: bold; }
/**
* Address styling not present in Safari 5 and Chrome.
*/
dfn {
font-style: italic; }
/**
* Address differences between Firefox and other browsers.
*/
hr {
-moz-box-sizing: content-box;
box-sizing: content-box;
height: 0; }
/**
* Correct font family set oddly in Safari 5 and Chrome.
*/
code,
kbd,
pre,
samp {
font-size: 1em;
font-family: monospace, serif; }
/**
* Improve readability of pre-formatted text in all browsers.
*/
pre {
white-space: pre-wrap; }
/**
* Set consistent quote types.
*/
q {
quotes: "\201C" "\201D" "\2018" "\2019"; }
/**
* Address inconsistent and variable font size in all browsers.
*/
small {
font-size: 80%; }
/**
* Prevent `sub` and `sup` affecting `line-height` in all browsers.
*/
sub,
sup {
position: relative;
vertical-align: baseline;
font-size: 75%;
line-height: 0; }
sup {
top: -0.5em; }
sub {
bottom: -0.25em; }
/**
* Define consistent border, margin, and padding.
*/
fieldset {
margin: 0 2px;
padding: 0.35em 0.625em 0.75em;
border: 1px solid #c0c0c0; }
/**
* 1. Correct `color` not being inherited in IE 8/9.
* 2. Remove padding so people aren't caught out if they zero out fieldsets.
*/
legend {
padding: 0;
/* 2 */
border: 0;
/* 1 */ }
/**
* 1. Correct font family not being inherited in all browsers.
* 2. Correct font size not being inherited in all browsers.
* 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome.
* 4. Remove any default :focus styles
* 5. Make sure webkit font smoothing is being inherited
* 6. Remove default gradient in Android Firefox / FirefoxOS
*/
button,
input,
select,
textarea {
margin: 0;
/* 3 */
font-size: 100%;
/* 2 */
font-family: inherit;
/* 1 */
outline-offset: 0;
/* 4 */
outline-style: none;
/* 4 */
outline-width: 0;
/* 4 */
-webkit-font-smoothing: inherit;
/* 5 */
background-image: none;
/* 6 */ }
/**
* Address Firefox 4+ setting `line-height` on `input` using `importnt` in
* the UA stylesheet.
*/
button,
input {
line-height: normal; }
/**
* Address inconsistent `text-transform` inheritance for `button` and `select`.
* All other form control elements do not inherit `text-transform` values.
* Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+.
* Correct `select` style inheritance in Firefox 4+ and Opera.
*/
button,
select {
text-transform: none; }
/**
* 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
* and `video` controls.
* 2. Correct inability to style clickable `input` types in iOS.
* 3. Improve usability and consistency of cursor style between image-type
* `input` and others.
*/
button,
html input[type="button"],
input[type="reset"],
input[type="submit"] {
cursor: pointer;
/* 3 */
-webkit-appearance: button;
/* 2 */ }
/**
* Re-set default cursor for disabled elements.
*/
button[disabled],
html input[disabled] {
cursor: default; }
/**
* 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.
* 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome
* (include `-moz` to future-proof).
*/
input[type="search"] {
-webkit-box-sizing: content-box;
/* 2 */
-moz-box-sizing: content-box;
box-sizing: content-box;
-webkit-appearance: textfield;
/* 1 */ }
/**
* Remove inner padding and search cancel button in Safari 5 and Chrome
* on OS X.
*/
input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-decoration {
-webkit-appearance: none; }
/**
* Remove inner padding and border in Firefox 4+.
*/
button::-moz-focus-inner,
input::-moz-focus-inner {
padding: 0;
border: 0; }
/**
* 1. Remove default vertical scrollbar in IE 8/9.
* 2. Improve readability and alignment in all browsers.
*/
textarea {
overflow: auto;
/* 1 */
vertical-align: top;
/* 2 */ }
img {
-webkit-user-drag: none; }
/* ==========================================================================
Tables
========================================================================== */
/**
* Remove most spacing between table cells.
*/
table {
border-spacing: 0;
border-collapse: collapse; }
/**
* Scaffolding
* --------------------------------------------------
*/
*,
*:before,
*:after {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box; }
html {
overflow: hidden;
-ms-touch-action: pan-y;
touch-action: pan-y; }
body,
.ionic-body {
-webkit-touch-callout: none;
-webkit-font-smoothing: antialiased;
font-smoothing: antialiased;
-webkit-text-size-adjust: none;
-moz-text-size-adjust: none;
text-size-adjust: none;
-webkit-tap-highlight-color: transparent;
-webkit-tap-highlight-color: transparent;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
top: 0;
right: 0;
bottom: 0;
left: 0;
overflow: hidden;
margin: 0;
padding: 0;
color: #000;
word-wrap: break-word;
font-size: 14px;
font-family: -apple-system;
font-family: "-apple-system", "Helvetica Neue", "Roboto", "Segoe UI", sans-serif;
line-height: 20px;
text-rendering: optimizeLegibility;
-webkit-backface-visibility: hidden;
-webkit-user-drag: none;
-ms-content-zooming: none; }
body.grade-b,
body.grade-c {
text-rendering: auto; }
.content {
position: relative; }
.scroll-content {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
overflow: hidden;
margin-top: -1px;
padding-top: 1px;
margin-bottom: -1px;
width: auto;
height: auto; }
.menu .scroll-content.scroll-content-false {
z-index: 11; }
.scroll-view {
position: relative;
display: block;
overflow: hidden;
margin-top: -1px; }
.scroll-view.overflow-scroll {
position: relative; }
.scroll-view.scroll-x {
overflow-x: scroll;
overflow-y: hidden; }
.scroll-view.scroll-y {
overflow-x: hidden;
overflow-y: scroll; }
.scroll-view.scroll-xy {
overflow-x: scroll;
overflow-y: scroll; }
/**
* Scroll is the scroll view component available for complex and custom
* scroll view functionality.
*/
.scroll {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-touch-callout: none;
-webkit-text-size-adjust: none;
-moz-text-size-adjust: none;
text-size-adjust: none;
-webkit-transform-origin: left top;
transform-origin: left top; }
/**
* Set ms-viewport to prevent MS "page squish" and allow fluid scrolling
* https://msdn.microsoft.com/en-us/library/ie/hh869615(v=vs.85).aspx
*/
@-ms-viewport {
width: device-width; }
.scroll-bar {
position: absolute;
z-index: 9999; }
.ng-animate .scroll-bar {
visibility: hidden; }
.scroll-bar-h {
right: 2px;
bottom: 3px;
left: 2px;
height: 3px; }
.scroll-bar-h .scroll-bar-indicator {
height: 100%; }
.scroll-bar-v {
top: 2px;
right: 3px;
bottom: 2px;
width: 3px; }
.scroll-bar-v .scroll-bar-indicator {
width: 100%; }
.scroll-bar-indicator {
position: absolute;
border-radius: 4px;
background: rgba(0, 0, 0, 0.3);
opacity: 1;
-webkit-transition: opacity 0.3s linear;
transition: opacity 0.3s linear; }
.scroll-bar-indicator.scroll-bar-fade-out {
opacity: 0; }
.platform-android .scroll-bar-indicator {
border-radius: 0; }
.grade-b .scroll-bar-indicator,
.grade-c .scroll-bar-indicator {
background: #aaa; }
.grade-b .scroll-bar-indicator.scroll-bar-fade-out,
.grade-c .scroll-bar-indicator.scroll-bar-fade-out {
-webkit-transition: none;
transition: none; }
ion-infinite-scroll {
height: 60px;
width: 100%;
display: block;
display: -webkit-box;
display: -webkit-flex;
display: -moz-box;
display: -moz-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-direction: normal;
-webkit-box-orient: horizontal;
-webkit-flex-direction: row;
-moz-flex-direction: row;
-ms-flex-direction: row;
flex-direction: row;
-webkit-box-pack: center;
-ms-flex-pack: center;
-webkit-justify-content: center;
-moz-justify-content: center;
justify-content: center;
-webkit-box-align: center;
-ms-flex-align: center;
-webkit-align-items: center;
-moz-align-items: center;
align-items: center; }
ion-infinite-scroll .icon {
color: #666666;
font-size: 30px;
color: #666666; }
ion-infinite-scroll:not(.active) .spinner,
ion-infinite-scroll:not(.active) .icon:before {
display: none; }
.overflow-scroll {
overflow-x: hidden;
overflow-y: scroll;
-webkit-overflow-scrolling: touch;
-ms-overflow-style: -ms-autohiding-scrollbar;
top: 0;
right: 0;
bottom: 0;
left: 0;
position: absolute; }
.overflow-scroll.pane {
overflow-x: hidden;
overflow-y: scroll; }
.overflow-scroll .scroll {
position: static;
height: 100%;
-webkit-transform: translate3d(0, 0, 0); }
.overflow-scroll.keyboard-up:not(.keyboard-up-confirm) {
overflow: hidden; }
/* If you change these, change platform.scss as well */
.has-header {
top: 44px; }
.no-header {
top: 0; }
.has-subheader {
top: 88px; }
.has-tabs-top {
top: 93px; }
.has-header.has-subheader.has-tabs-top {
top: 137px; }
.has-footer {
bottom: 44px; }
.has-subfooter {
bottom: 88px; }
.has-tabs,
.bar-footer.has-tabs {
bottom: 49px; }
.has-tabs.pane,
.bar-footer.has-tabs.pane {
bottom: 49px;
height: auto; }
.bar-subfooter.has-tabs {
bottom: 93px; }
.has-footer.has-tabs {
bottom: 93px; }
.pane {
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
-webkit-transition-duration: 0;
transition-duration: 0;
z-index: 1; }
.view {
z-index: 1; }
.pane,
.view {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
background-color: #fff;
overflow: hidden; }
.view-container {
position: absolute;
display: block;
width: 100%;
height: 100%; }
/**
* Typography
* --------------------------------------------------
*/
p {
margin: 0 0 10px; }
small {
font-size: 85%; }
cite {
font-style: normal; }
.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 {
color: #000;
font-weight: 500;
font-family: "-apple-system", "Helvetica Neue", "Roboto", "Segoe UI", sans-serif;
line-height: 1.2; }
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; }
h1, .h1,
h2, .h2,
h3, .h3 {
margin-top: 20px;
margin-bottom: 10px; }
h1:first-child, .h1:first-child,
h2:first-child, .h2:first-child,
h3:first-child, .h3:first-child {
margin-top: 0; }
h1 + h1, h1 + .h1,
h1 + h2, h1 + .h2,
h1 + h3, h1 + .h3, .h1 + h1, .h1 + .h1,
.h1 + h2, .h1 + .h2,
.h1 + h3, .h1 + .h3,
h2 + h1,
h2 + .h1,
h2 + h2,
h2 + .h2,
h2 + h3,
h2 + .h3, .h2 + h1, .h2 + .h1,
.h2 + h2, .h2 + .h2,
.h2 + h3, .h2 + .h3,
h3 + h1,
h3 + .h1,
h3 + h2,
h3 + .h2,
h3 + h3,
h3 + .h3, .h3 + h1, .h3 + .h1,
.h3 + h2, .h3 + .h2,
.h3 + h3, .h3 + .h3 {
margin-top: 10px; }
h4, .h4,
h5, .h5,
h6, .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; }
dl {
margin-bottom: 20px; }
dt,
dd {
line-height: 1.42857; }
dt {
font-weight: bold; }
blockquote {
margin: 0 0 20px;
padding: 10px 20px;
border-left: 5px solid gray; }
blockquote p {
font-weight: 300;
font-size: 17.5px;
line-height: 1.25; }
blockquote p:last-child {
margin-bottom: 0; }
blockquote small {
display: block;
line-height: 1.42857; }
blockquote small:before {
content: '\2014 \00A0'; }
q:before,
q:after,
blockquote:before,
blockquote:after {
content: ""; }
address {
display: block;
margin-bottom: 20px;
font-style: normal;
line-height: 1.42857; }
a {
color: #387ef5; }
a.subdued {
padding-right: 10px;
color: #888;
text-decoration: none; }
a.subdued:hover {
text-decoration: none; }
a.subdued:last-child {
padding-right: 0; }
/**
* Action Sheets
* --------------------------------------------------
*/
.action-sheet-backdrop {
-webkit-transition: background-color 150ms ease-in-out;
transition: background-color 150ms ease-in-out;
position: fixed;
top: 0;
left: 0;
z-index: 11;
width: 100%;
height: 100%;
background-color: transparent; }
.action-sheet-backdrop.active {
background-color: rgba(0, 0, 0, 0.4); }
.action-sheet-wrapper {
-webkit-transform: translate3d(0, 100%, 0);
transform: translate3d(0, 100%, 0);
-webkit-transition: all cubic-bezier(0.36, 0.66, 0.04, 1) 500ms;
transition: all cubic-bezier(0.36, 0.66, 0.04, 1) 500ms;
position: absolute;
bottom: 0;
left: 0;
right: 0;
width: 100%;
max-width: 500px;
margin: auto; }
.action-sheet-up {
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0); }
.action-sheet {
margin-left: 8px;
margin-right: 8px;
width: auto;
z-index: 11;
overflow: hidden; }
.action-sheet .button {
display: block;
padding: 1px;
width: 100%;
border-radius: 0;
border-color: #d1d3d6;
background-color: transparent;
color: #007aff;
font-size: 21px; }
.action-sheet .button:hover {
color: #007aff; }
.action-sheet .button.destructive {
color: #ff3b30; }
.action-sheet .button.destructive:hover {
color: #ff3b30; }
.action-sheet .button.active, .action-sheet .button.activated {
box-shadow: none;
border-color: #d1d3d6;
color: #007aff;
background: #e4e5e7; }
.action-sheet-has-icons .icon {
position: absolute;
left: 16px; }
.action-sheet-title {
padding: 16px;
color: #8f8f8f;
text-align: center;
font-size: 13px; }
.action-sheet-group {
margin-bottom: 8px;
border-radius: 4px;
background-color: #fff;
overflow: hidden; }
.action-sheet-group .button {
border-width: 1px 0px 0px 0px; }
.action-sheet-group .button:first-child:last-child {
border-width: 0; }
.action-sheet-options {
background: #f1f2f3; }
.action-sheet-cancel .button {
font-weight: 500; }
.action-sheet-open {
pointer-events: none; }
.action-sheet-open.modal-open .modal {
pointer-events: none; }
.action-sheet-open .action-sheet-backdrop {
pointer-events: auto; }
.platform-android .action-sheet-backdrop.active {
background-color: rgba(0, 0, 0, 0.2); }
.platform-android .action-sheet {
margin: 0; }
.platform-android .action-sheet .action-sheet-title,
.platform-android .action-sheet .button {
text-align: left;
border-color: transparent;
font-size: 16px;
color: inherit; }
.platform-android .action-sheet .action-sheet-title {
font-size: 14px;
padding: 16px;
color: #666; }
.platform-android .action-sheet .button.active,
.platform-android .action-sheet .button.activated {
background: #e8e8e8; }
.platform-android .action-sheet-group {
margin: 0;
border-radius: 0;
background-color: #fafafa; }
.platform-android .action-sheet-cancel {
display: none; }
.platform-android .action-sheet-has-icons .button {
padding-left: 56px; }
.backdrop {
position: fixed;
top: 0;
left: 0;
z-index: 11;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.4);
visibility: hidden;
opacity: 0;
-webkit-transition: 0.1s opacity linear;
transition: 0.1s opacity linear; }
.backdrop.visible {
visibility: visible; }
.backdrop.active {
opacity: 1; }
/**
* Bar (Headers and Footers)
* --------------------------------------------------
*/
.bar {
display: -webkit-box;
display: -webkit-flex;
display: -moz-box;
display: -moz-flex;
display: -ms-flexbox;
display: flex;
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
position: absolute;
right: 0;
left: 0;
z-index: 9;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
padding: 5px;
width: 100%;
height: 44px;
border-width: 0;
border-style: solid;
border-top: 1px solid transparent;
border-bottom: 1px solid #ddd;
background-color: white;
/* border-width: 1px will actually create 2 device pixels on retina */
/* this nifty trick sets an actual 1px border on hi-res displays */
background-size: 0; }
@media (min--moz-device-pixel-ratio: 1.5), (-webkit-min-device-pixel-ratio: 1.5), (min-device-pixel-ratio: 1.5), (min-resolution: 144dpi), (min-resolution: 1.5dppx) {
.bar {
border: none;
background-image: linear-gradient(0deg, #ddd, #ddd 50%, transparent 50%);
background-position: bottom;
background-size: 100% 1px;
background-repeat: no-repeat; } }
.bar.bar-clear {
border: none;
background: none;
color: #fff; }
.bar.bar-clear .button {
color: #fff; }
.bar.bar-clear .title {
color: #fff; }
.bar.item-input-inset .item-input-wrapper {
margin-top: -1px; }
.bar.item-input-inset .item-input-wrapper input {
padding-left: 8px;
width: 94%;
height: 28px;
background: transparent; }
.bar.bar-light {
border-color: #ddd;
background-color: white;
background-image: linear-gradient(0deg, #ddd, #ddd 50%, transparent 50%);
color: #444; }
.bar.bar-light .title {
color: #444; }
.bar.bar-light.bar-footer {
background-image: linear-gradient(180deg, #ddd, #ddd 50%, transparent 50%); }
.bar.bar-stable {
border-color: #b2b2b2;
background-color: #f8f8f8;
background-image: linear-gradient(0deg, #b2b2b2, #b2b2b2 50%, transparent 50%);
color: #444; }
.bar.bar-stable .title {
color: #444; }
.bar.bar-stable.bar-footer {
background-image: linear-gradient(180deg, #b2b2b2, #b2b2b2 50%, transparent 50%); }
.bar.bar-positive {
border-color: #0c60ee;
background-color: #387ef5;
background-image: linear-gradient(0deg, #0c60ee, #0c60ee 50%, transparent 50%);
color: #fff; }
.bar.bar-positive .title {
color: #fff; }
.bar.bar-positive.bar-footer {
background-image: linear-gradient(180deg, #0c60ee, #0c60ee 50%, transparent 50%); }
.bar.bar-calm {
border-color: #0a9dc7;
background-color: #11c1f3;
background-image: linear-gradient(0deg, #0a9dc7, #0a9dc7 50%, transparent 50%);
color: #fff; }
.bar.bar-calm .title {
color: #fff; }
.bar.bar-calm.bar-footer {
background-image: linear-gradient(180deg, #0a9dc7, #0a9dc7 50%, transparent 50%); }
.bar.bar-assertive {
border-color: #e42112;
background-color: #ef473a;
background-image: linear-gradient(0deg, #e42112, #e42112 50%, transparent 50%);
color: #fff; }
.bar.bar-assertive .title {
color: #fff; }
.bar.bar-assertive.bar-footer {
background-image: linear-gradient(180deg, #e42112, #e42112 50%, transparent 50%); }
.bar.bar-balanced {
border-color: #28a54c;
background-color: #33cd5f;
background-image: linear-gradient(0deg, #28a54c, #28a54c 50%, transparent 50%);
color: #fff; }
.bar.bar-balanced .title {
color: #fff; }
.bar.bar-balanced.bar-footer {
background-image: linear-gradient(180deg, #28a54c, #0c60ee 50%, transparent 50%); }
.bar.bar-energized {
border-color: #e6b500;
background-color: #ffc900;
background-image: linear-gradient(0deg, #e6b500, #e6b500 50%, transparent 50%);
color: #fff; }
.bar.bar-energized .title {
color: #fff; }
.bar.bar-energized.bar-footer {
background-image: linear-gradient(180deg, #e6b500, #e6b500 50%, transparent 50%); }
.bar.bar-royal {
border-color: #6b46e5;
background-color: #886aea;
background-image: linear-gradient(0deg, #6b46e5, #6b46e5 50%, transparent 50%);
color: #fff; }
.bar.bar-royal .title {
color: #fff; }
.bar.bar-royal.bar-footer {
background-image: linear-gradient(180deg, #6b46e5, #6b46e5 50%, transparent 50%); }
.bar.bar-dark {
border-color: #111;
background-color: #444444;
background-image: linear-gradient(0deg, #111, #111 50%, transparent 50%);
color: #fff; }
.bar.bar-dark .title {
color: #fff; }
.bar.bar-dark.bar-footer {
background-image: linear-gradient(180deg, #111, #111 50%, transparent 50%); }
.bar .title {
display: block;
position: absolute;
top: 0;
right: 0;
left: 0;
z-index: 0;
overflow: hidden;
margin: 0 10px;
min-width: 30px;
height: 43px;
text-align: center;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 17px;
font-weight: 500;
line-height: 44px; }
.bar .title.title-left {
text-align: left; }
.bar .title.title-right {
text-align: right; }
.bar .title a {
color: inherit; }
.bar .button, .bar button {
z-index: 1;
padding: 0 8px;
min-width: initial;
min-height: 31px;
font-weight: 400;
font-size: 13px;
line-height: 32px; }
.bar .button.button-icon:before,
.bar .button .icon:before, .bar .button.icon:before, .bar .button.icon-left:before, .bar .button.icon-right:before, .bar button.button-icon:before,
.bar button .icon:before, .bar button.icon:before, .bar button.icon-left:before, .bar button.icon-right:before {
padding-right: 2px;
padding-left: 2px;
font-size: 20px;
line-height: 32px; }
.bar .button.button-icon, .bar button.button-icon {
font-size: 17px; }
.bar .button.button-icon .icon:before, .bar .button.button-icon:before, .bar .button.button-icon.icon-left:before, .bar .button.button-icon.icon-right:before, .bar button.button-icon .icon:before, .bar button.button-icon:before, .bar button.button-icon.icon-left:before, .bar button.button-icon.icon-right:before {
vertical-align: top;
font-size: 32px;
line-height: 32px; }
.bar .button.button-clear, .bar button.button-clear {
padding-right: 2px;
padding-left: 2px;
font-weight: 300;
font-size: 17px; }
.bar .button.button-clear .icon:before, .bar .button.button-clear.icon:before, .bar .button.button-clear.icon-left:before, .bar .button.button-clear.icon-right:before, .bar button.button-clear .icon:before, .bar button.button-clear.icon:before, .bar button.button-clear.icon-left:before, .bar button.button-clear.icon-right:before {
font-size: 32px;
line-height: 32px; }
.bar .button.back-button, .bar button.back-button {
display: block;
margin-right: 5px;
padding: 0;
white-space: nowrap;
font-weight: 400; }
.bar .button.back-button.active, .bar .button.back-button.activated, .bar button.back-button.active, .bar button.back-button.activated {
opacity: 0.2; }
.bar .button-bar > .button,
.bar .buttons > .button {
min-height: 31px;
line-height: 32px; }
.bar .button-bar + .button,
.bar .button + .button-bar {
margin-left: 5px; }
.bar .buttons,
.bar .buttons.primary-buttons,
.bar .buttons.secondary-buttons {
display: inherit; }
.bar .buttons span {
display: inline-block; }
.bar .buttons-left span {
margin-right: 5px;
display: inherit; }
.bar .buttons-right span {
margin-left: 5px;
display: inherit; }
.bar .title + .button:last-child,
.bar > .button + .button:last-child,
.bar > .button.pull-right,
.bar .buttons.pull-right,
.bar .title + .buttons {
position: absolute;
top: 5px;
right: 5px;
bottom: 5px; }
.platform-android .nav-bar-has-subheader .bar {
background-image: none; }
.platform-android .bar .back-button .icon:before {
font-size: 24px; }
.platform-android .bar .title {
font-size: 19px;
line-height: 44px; }
.bar-light .button {
border-color: transparent;
background-color: white;
color: #444; }
.bar-light .button:hover {
color: #444;
text-decoration: none; }
.bar-light .button.active, .bar-light .button.activated {
background-color: #fafafa; }
.bar-light .button.button-clear {
border-color: transparent;
background: none;
box-shadow: none;
color: #444;
font-size: 17px; }
.bar-light .button.button-icon {
border-color: transparent;
background: none; }
.bar-stable .button {
border-color: transparent;
background-color: #f8f8f8;
color: #444; }
.bar-stable .button:hover {
color: #444;
text-decoration: none; }
.bar-stable .button.active, .bar-stable .button.activated {
background-color: #e5e5e5; }
.bar-stable .button.button-clear {
border-color: transparent;
background: none;
box-shadow: none;
color: #444;
font-size: 17px; }
.bar-stable .button.button-icon {
border-color: transparent;
background: none; }
.bar-positive .button {
border-color: transparent;
background-color: #387ef5;
color: #fff; }
.bar-positive .button:hover {
color: #fff;
text-decoration: none; }
.bar-positive .button.active, .bar-positive .button.activated {
background-color: #0c60ee; }
.bar-positive .button.button-clear {
border-color: transparent;
background: none;
box-shadow: none;
color: #fff;
font-size: 17px; }
.bar-positive .button.button-icon {
border-color: transparent;
background: none; }
.bar-calm .button {
border-color: transparent;
background-color: #11c1f3;
color: #fff; }
.bar-calm .button:hover {
color: #fff;
text-decoration: none; }
.bar-calm .button.active, .bar-calm .button.activated {
background-color: #0a9dc7; }
.bar-calm .button.button-clear {
border-color: transparent;
background: none;
box-shadow: none;
color: #fff;
font-size: 17px; }
.bar-calm .button.button-icon {
border-color: transparent;
background: none; }
.bar-assertive .button {
border-color: transparent;
background-color: #ef473a;
color: #fff; }
.bar-assertive .button:hover {
color: #fff;
text-decoration: none; }
.bar-assertive .button.active, .bar-assertive .button.activated {
background-color: #e42112; }
.bar-assertive .button.button-clear {
border-color: transparent;
background: none;
box-shadow: none;
color: #fff;
font-size: 17px; }
.bar-assertive .button.button-icon {
border-color: transparent;
background: none; }
.bar-balanced .button {
border-color: transparent;
background-color: #33cd5f;
color: #fff; }
.bar-balanced .button:hover {
color: #fff;
text-decoration: none; }
.bar-balanced .button.active, .bar-balanced .button.activated {
background-color: #28a54c; }
.bar-balanced .button.button-clear {
border-color: transparent;
background: none;
box-shadow: none;
color: #fff;
font-size: 17px; }
.bar-balanced .button.button-icon {
border-color: transparent;
background: none; }
.bar-energized .button {
border-color: transparent;
background-color: #ffc900;
color: #fff; }
.bar-energized .button:hover {
color: #fff;
text-decoration: none; }
.bar-energized .button.active, .bar-energized .button.activated {
background-color: #e6b500; }
.bar-energized .button.button-clear {
border-color: transparent;
background: none;
box-shadow: none;
color: #fff;
font-size: 17px; }
.bar-energized .button.button-icon {
border-color: transparent;
background: none; }
.bar-royal .button {
border-color: transparent;
background-color: #886aea;
color: #fff; }
.bar-royal .button:hover {
color: #fff;
text-decoration: none; }
.bar-royal .button.active, .bar-royal .button.activated {
background-color: #6b46e5; }
.bar-royal .button.button-clear {
border-color: transparent;
background: none;
box-shadow: none;
color: #fff;
font-size: 17px; }
.bar-royal .button.button-icon {
border-color: transparent;
background: none; }
.bar-dark .button {
border-color: transparent;
background-color: #444444;
color: #fff; }
.bar-dark .button:hover {
color: #fff;
text-decoration: none; }
.bar-dark .button.active, .bar-dark .button.activated {
background-color: #262626; }
.bar-dark .button.button-clear {
border-color: transparent;
background: none;
box-shadow: none;
color: #fff;
font-size: 17px; }
.bar-dark .button.button-icon {
border-color: transparent;
background: none; }
.bar-header {
top: 0;
border-top-width: 0;
border-bottom-width: 1px; }
.bar-header.has-tabs-top {
border-bottom-width: 0px;
background-image: none; }
.tabs-top .bar-header {
border-bottom-width: 0px;
background-image: none; }
.bar-footer {
bottom: 0;
border-top-width: 1px;
border-bottom-width: 0;
background-position: top;
height: 44px; }
.bar-footer.item-input-inset {
position: absolute; }
.bar-tabs {
padding: 0; }
.bar-subheader {
top: 44px;
display: block;
height: 44px; }
.bar-subfooter {
bottom: 44px;
display: block;
height: 44px; }
.nav-bar-block {
position: absolute;
top: 0;
right: 0;
left: 0;
z-index: 9; }
.bar .back-button.hide,
.bar .buttons .hide {
display: none; }
.nav-bar-tabs-top .bar {
background-image: none; }
/**
* Tabs
* --------------------------------------------------
* A navigation bar with any number of tab items supported.
*/
.tabs {
display: -webkit-box;
display: -webkit-flex;
display: -moz-box;
display: -moz-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-direction: normal;
-webkit-box-orient: horizontal;
-webkit-flex-direction: horizontal;
-moz-flex-direction: horizontal;
-ms-flex-direction: horizontal;
flex-direction: horizontal;
-webkit-box-pack: center;
-ms-flex-pack: center;
-webkit-justify-content: center;
-moz-justify-content: center;
justify-content: center;
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
border-color: #b2b2b2;
background-color: #f8f8f8;
background-image: linear-gradient(0deg, #b2b2b2, #b2b2b2 50%, transparent 50%);
color: #444;
position: absolute;
bottom: 0;
z-index: 5;
width: 100%;
height: 49px;
border-style: solid;
border-top-width: 1px;
background-size: 0;
line-height: 49px; }
.tabs .tab-item .badge {
background-color: #444;
color: #f8f8f8; }
@media (min--moz-device-pixel-ratio: 1.5), (-webkit-min-device-pixel-ratio: 1.5), (min-device-pixel-ratio: 1.5), (min-resolution: 144dpi), (min-resolution: 1.5dppx) {
.tabs {
padding-top: 2px;
border-top: none !important;
border-bottom: none;
background-position: top;
background-size: 100% 1px;
background-repeat: no-repeat; } }
/* Allow parent element of tabs to define color, or just the tab itself */
.tabs-light > .tabs,
.tabs.tabs-light {
border-color: #ddd;
background-color: #fff;
background-image: linear-gradient(0deg, #ddd, #ddd 50%, transparent 50%);
color: #444; }
.tabs-light > .tabs .tab-item .badge,
.tabs.tabs-light .tab-item .badge {
background-color: #444;
color: #fff; }
.tabs-stable > .tabs,
.tabs.tabs-stable {
border-color: #b2b2b2;
background-color: #f8f8f8;
background-image: linear-gradient(0deg, #b2b2b2, #b2b2b2 50%, transparent 50%);
color: #444; }
.tabs-stable > .tabs .tab-item .badge,
.tabs.tabs-stable .tab-item .badge {
background-color: #444;
color: #f8f8f8; }
.tabs-positive > .tabs,
.tabs.tabs-positive {
border-color: #0c60ee;
background-color: #387ef5;
background-image: linear-gradient(0deg, #0c60ee, #0c60ee 50%, transparent 50%);
color: #fff; }
.tabs-positive > .tabs .tab-item .badge,
.tabs.tabs-positive .tab-item .badge {
background-color: #fff;
color: #387ef5; }
.tabs-calm > .tabs,
.tabs.tabs-calm {
border-color: #0a9dc7;
background-color: #11c1f3;
background-image: linear-gradient(0deg, #0a9dc7, #0a9dc7 50%, transparent 50%);
color: #fff; }
.tabs-calm > .tabs .tab-item .badge,
.tabs.tabs-calm .tab-item .badge {
background-color: #fff;
color: #11c1f3; }
.tabs-assertive > .tabs,
.tabs.tabs-assertive {
border-color: #e42112;
background-color: #ef473a;
background-image: linear-gradient(0deg, #e42112, #e42112 50%, transparent 50%);
color: #fff; }
.tabs-assertive > .tabs .tab-item .badge,
.tabs.tabs-assertive .tab-item .badge {
background-color: #fff;
color: #ef473a; }
.tabs-balanced > .tabs,
.tabs.tabs-balanced {
border-color: #28a54c;
background-color: #33cd5f;
background-image: linear-gradient(0deg, #28a54c, #28a54c 50%, transparent 50%);
color: #fff; }
.tabs-balanced > .tabs .tab-item .badge,
.tabs.tabs-balanced .tab-item .badge {
background-color: #fff;
color: #33cd5f; }
.tabs-energized > .tabs,
.tabs.tabs-energized {
border-color: #e6b500;
background-color: #ffc900;
background-image: linear-gradient(0deg, #e6b500, #e6b500 50%, transparent 50%);
color: #fff; }
.tabs-energized > .tabs .tab-item .badge,
.tabs.tabs-energized .tab-item .badge {
background-color: #fff;
color: #ffc900; }
.tabs-royal > .tabs,
.tabs.tabs-royal {
border-color: #6b46e5;
background-color: #886aea;
background-image: linear-gradient(0deg, #6b46e5, #6b46e5 50%, transparent 50%);
color: #fff; }
.tabs-royal > .tabs .tab-item .badge,
.tabs.tabs-royal .tab-item .badge {
background-color: #fff;
color: #886aea; }
.tabs-dark > .tabs,
.tabs.tabs-dark {
border-color: #111;
background-color: #444;
background-image: linear-gradient(0deg, #111, #111 50%, transparent 50%);
color: #fff; }
.tabs-dark > .tabs .tab-item .badge,
.tabs.tabs-dark .tab-item .badge {
background-color: #fff;
color: #444; }
.tabs-striped .tabs {
background-color: white;
background-image: none;
border: none;
border-bottom: 1px solid #ddd;
padding-top: 2px; }
.tabs-striped .tab-item.tab-item-active, .tabs-striped .tab-item.active, .tabs-striped .tab-item.activated {
margin-top: -2px;
border-style: solid;
border-width: 2px 0 0 0;
border-color: #444; }
.tabs-striped .tab-item.tab-item-active .badge, .tabs-striped .tab-item.active .badge, .tabs-striped .tab-item.activated .badge {
top: 2px;
opacity: 1; }
.tabs-striped.tabs-light .tabs {
background-color: #fff; }
.tabs-striped.tabs-light .tab-item {
color: rgba(68, 68, 68, 0.4);
opacity: 1; }
.tabs-striped.tabs-light .tab-item .badge {
opacity: 0.4; }
.tabs-striped.tabs-light .tab-item.tab-item-active, .tabs-striped.tabs-light .tab-item.active, .tabs-striped.tabs-light .tab-item.activated {
margin-top: -2px;
color: #444;
border-style: solid;
border-width: 2px 0 0 0;
border-color: #444; }
.tabs-striped.tabs-top .tab-item.tab-item-active .badge, .tabs-striped.tabs-top .tab-item.active .badge, .tabs-striped.tabs-top .tab-item.activated .badge {
top: 4%; }
.tabs-striped.tabs-stable .tabs {
background-color: #f8f8f8; }
.tabs-striped.tabs-stable .tab-item {
color: rgba(68, 68, 68, 0.4);
opacity: 1; }
.tabs-striped.tabs-stable .tab-item .badge {
opacity: 0.4; }
.tabs-striped.tabs-stable .tab-item.tab-item-active, .tabs-striped.tabs-stable .tab-item.active, .tabs-striped.tabs-stable .tab-item.activated {
margin-top: -2px;
color: #444;
border-style: solid;
border-width: 2px 0 0 0;
border-color: #444; }
.tabs-striped.tabs-top .tab-item.tab-item-active .badge, .tabs-striped.tabs-top .tab-item.active .badge, .tabs-striped.tabs-top .tab-item.activated .badge {
top: 4%; }
.tabs-striped.tabs-positive .tabs {
background-color: #387ef5; }
.tabs-striped.tabs-positive .tab-item {
color: rgba(255, 255, 255, 0.4);
opacity: 1; }
.tabs-striped.tabs-positive .tab-item .badge {
opacity: 0.4; }
.tabs-striped.tabs-positive .tab-item.tab-item-active, .tabs-striped.tabs-positive .tab-item.active, .tabs-striped.tabs-positive .tab-item.activated {
margin-top: -2px;
color: #fff;
border-style: solid;
border-width: 2px 0 0 0;
border-color: #fff; }
.tabs-striped.tabs-top .tab-item.tab-item-active .badge, .tabs-striped.tabs-top .tab-item.active .badge, .tabs-striped.tabs-top .tab-item.activated .badge {
top: 4%; }
.tabs-striped.tabs-calm .tabs {
background-color: #11c1f3; }
.tabs-striped.tabs-calm .tab-item {
color: rgba(255, 255, 255, 0.4);
opacity: 1; }
.tabs-striped.tabs-calm .tab-item .badge {
opacity: 0.4; }
.tabs-striped.tabs-calm .tab-item.tab-item-active, .tabs-striped.tabs-calm .tab-item.active, .tabs-striped.tabs-calm .tab-item.activated {
margin-top: -2px;
color: #fff;
border-style: solid;
border-width: 2px 0 0 0;
border-color: #fff; }
.tabs-striped.tabs-top .tab-item.tab-item-active .badge, .tabs-striped.tabs-top .tab-item.active .badge, .tabs-striped.tabs-top .tab-item.activated .badge {
top: 4%; }
.tabs-striped.tabs-assertive .tabs {
background-color: #ef473a; }
.tabs-striped.tabs-assertive .tab-item {
color: rgba(255, 255, 255, 0.4);
opacity: 1; }
.tabs-striped.tabs-assertive .tab-item .badge {
opacity: 0.4; }
.tabs-striped.tabs-assertive .tab-item.tab-item-active, .tabs-striped.tabs-assertive .tab-item.active, .tabs-striped.tabs-assertive .tab-item.activated {
margin-top: -2px;
color: #fff;
border-style: solid;
border-width: 2px 0 0 0;
border-color: #fff; }
.tabs-striped.tabs-top .tab-item.tab-item-active .badge, .tabs-striped.tabs-top .tab-item.active .badge, .tabs-striped.tabs-top .tab-item.activated .badge {
top: 4%; }
.tabs-striped.tabs-balanced .tabs {
background-color: #33cd5f; }
.tabs-striped.tabs-balanced .tab-item {
color: rgba(255, 255, 255, 0.4);
opacity: 1; }
.tabs-striped.tabs-balanced .tab-item .badge {
opacity: 0.4; }
.tabs-striped.tabs-balanced .tab-item.tab-item-active, .tabs-striped.tabs-balanced .tab-item.active, .tabs-striped.tabs-balanced .tab-item.activated {
margin-top: -2px;
color: #fff;
border-style: solid;
border-width: 2px 0 0 0;
border-color: #fff; }
.tabs-striped.tabs-top .tab-item.tab-item-active .badge, .tabs-striped.tabs-top .tab-item.active .badge, .tabs-striped.tabs-top .tab-item.activated .badge {
top: 4%; }
.tabs-striped.tabs-energized .tabs {
background-color: #ffc900; }
.tabs-striped.tabs-energized .tab-item {
color: rgba(255, 255, 255, 0.4);
opacity: 1; }
.tabs-striped.tabs-energized .tab-item .badge {
opacity: 0.4; }
.tabs-striped.tabs-energized .tab-item.tab-item-active, .tabs-striped.tabs-energized .tab-item.active, .tabs-striped.tabs-energized .tab-item.activated {
margin-top: -2px;
color: #fff;
border-style: solid;
border-width: 2px 0 0 0;
border-color: #fff; }
.tabs-striped.tabs-top .tab-item.tab-item-active .badge, .tabs-striped.tabs-top .tab-item.active .badge, .tabs-striped.tabs-top .tab-item.activated .badge {
top: 4%; }
.tabs-striped.tabs-royal .tabs {
background-color: #886aea; }
.tabs-striped.tabs-royal .tab-item {
color: rgba(255, 255, 255, 0.4);
opacity: 1; }
.tabs-striped.tabs-royal .tab-item .badge {
opacity: 0.4; }
.tabs-striped.tabs-royal .tab-item.tab-item-active, .tabs-striped.tabs-royal .tab-item.active, .tabs-striped.tabs-royal .tab-item.activated {
margin-top: -2px;
color: #fff;
border-style: solid;
border-width: 2px 0 0 0;
border-color: #fff; }
.tabs-striped.tabs-top .tab-item.tab-item-active .badge, .tabs-striped.tabs-top .tab-item.active .badge, .tabs-striped.tabs-top .tab-item.activated .badge {
top: 4%; }
.tabs-striped.tabs-dark .tabs {
background-color: #444; }
.tabs-striped.tabs-dark .tab-item {
color: rgba(255, 255, 255, 0.4);
opacity: 1; }
.tabs-striped.tabs-dark .tab-item .badge {
opacity: 0.4; }
.tabs-striped.tabs-dark .tab-item.tab-item-active, .tabs-striped.tabs-dark .tab-item.active, .tabs-striped.tabs-dark .tab-item.activated {
margin-top: -2px;
color: #fff;
border-style: solid;
border-width: 2px 0 0 0;
border-color: #fff; }
.tabs-striped.tabs-top .tab-item.tab-item-active .badge, .tabs-striped.tabs-top .tab-item.active .badge, .tabs-striped.tabs-top .tab-item.activated .badge {
top: 4%; }
.tabs-striped.tabs-background-light .tabs {
background-color: #fff;
background-image: none; }
.tabs-striped.tabs-background-stable .tabs {
background-color: #f8f8f8;
background-image: none; }
.tabs-striped.tabs-background-positive .tabs {
background-color: #387ef5;
background-image: none; }
.tabs-striped.tabs-background-calm .tabs {
background-color: #11c1f3;
background-image: none; }
.tabs-striped.tabs-background-assertive .tabs {
background-color: #ef473a;
background-image: none; }
.tabs-striped.tabs-background-balanced .tabs {
background-color: #33cd5f;
background-image: none; }
.tabs-striped.tabs-background-energized .tabs {
background-color: #ffc900;
background-image: none; }
.tabs-striped.tabs-background-royal .tabs {
background-color: #886aea;
background-image: none; }
.tabs-striped.tabs-background-dark .tabs {
background-color: #444;
background-image: none; }
.tabs-striped.tabs-color-light .tab-item {
color: rgba(255, 255, 255, 0.4);
opacity: 1; }
.tabs-striped.tabs-color-light .tab-item .badge {
opacity: 0.4; }
.tabs-striped.tabs-color-light .tab-item.tab-item-active, .tabs-striped.tabs-color-light .tab-item.active, .tabs-striped.tabs-color-light .tab-item.activated {
margin-top: -2px;
color: #fff;
border: 0 solid #fff;
border-top-width: 2px; }
.tabs-striped.tabs-color-light .tab-item.tab-item-active .badge, .tabs-striped.tabs-color-light .tab-item.active .badge, .tabs-striped.tabs-color-light .tab-item.activated .badge {
top: 2px;
opacity: 1; }
.tabs-striped.tabs-color-stable .tab-item {
color: rgba(248, 248, 248, 0.4);
opacity: 1; }
.tabs-striped.tabs-color-stable .tab-item .badge {
opacity: 0.4; }
.tabs-striped.tabs-color-stable .tab-item.tab-item-active, .tabs-striped.tabs-color-stable .tab-item.active, .tabs-striped.tabs-color-stable .tab-item.activated {
margin-top: -2px;
color: #f8f8f8;
border: 0 solid #f8f8f8;
border-top-width: 2px; }
.tabs-striped.tabs-color-stable .tab-item.tab-item-active .badge, .tabs-striped.tabs-color-stable .tab-item.active .badge, .tabs-striped.tabs-color-stable .tab-item.activated .badge {
top: 2px;
opacity: 1; }
.tabs-striped.tabs-color-positive .tab-item {
color: rgba(56, 126, 245, 0.4);
opacity: 1; }
.tabs-striped.tabs-color-positive .tab-item .badge {
opacity: 0.4; }
.tabs-striped.tabs-color-positive .tab-item.tab-item-active, .tabs-striped.tabs-color-positive .tab-item.active, .tabs-striped.tabs-color-positive .tab-item.activated {
margin-top: -2px;
color: #387ef5;
border: 0 solid #387ef5;
border-top-width: 2px; }
.tabs-striped.tabs-color-positive .tab-item.tab-item-active .badge, .tabs-striped.tabs-color-positive .tab-item.active .badge, .tabs-striped.tabs-color-positive .tab-item.activated .badge {
top: 2px;
opacity: 1; }
.tabs-striped.tabs-color-calm .tab-item {
color: rgba(17, 193, 243, 0.4);
opacity: 1; }
.tabs-striped.tabs-color-calm .tab-item .badge {
opacity: 0.4; }
.tabs-striped.tabs-color-calm .tab-item.tab-item-active, .tabs-striped.tabs-color-calm .tab-item.active, .tabs-striped.tabs-color-calm .tab-item.activated {
margin-top: -2px;
color: #11c1f3;
border: 0 solid #11c1f3;
border-top-width: 2px; }
.tabs-striped.tabs-color-calm .tab-item.tab-item-active .badge, .tabs-striped.tabs-color-calm .tab-item.active .badge, .tabs-striped.tabs-color-calm .tab-item.activated .badge {
top: 2px;
opacity: 1; }
.tabs-striped.tabs-color-assertive .tab-item {
color: rgba(239, 71, 58, 0.4);
opacity: 1; }
.tabs-striped.tabs-color-assertive .tab-item .badge {
opacity: 0.4; }
.tabs-striped.tabs-color-assertive .tab-item.tab-item-active, .tabs-striped.tabs-color-assertive .tab-item.active, .tabs-striped.tabs-color-assertive .tab-item.activated {
margin-top: -2px;
color: #ef473a;
border: 0 solid #ef473a;
border-top-width: 2px; }
.tabs-striped.tabs-color-assertive .tab-item.tab-item-active .badge, .tabs-striped.tabs-color-assertive .tab-item.active .badge, .tabs-striped.tabs-color-assertive .tab-item.activated .badge {
top: 2px;
opacity: 1; }
.tabs-striped.tabs-color-balanced .tab-item {
color: rgba(51, 205, 95, 0.4);
opacity: 1; }
.tabs-striped.tabs-color-balanced .tab-item .badge {
opacity: 0.4; }
.tabs-striped.tabs-color-balanced .tab-item.tab-item-active, .tabs-striped.tabs-color-balanced .tab-item.active, .tabs-striped.tabs-color-balanced .tab-item.activated {
margin-top: -2px;
color: #33cd5f;
border: 0 solid #33cd5f;
border-top-width: 2px; }
.tabs-striped.tabs-color-balanced .tab-item.tab-item-active .badge, .tabs-striped.tabs-color-balanced .tab-item.active .badge, .tabs-striped.tabs-color-balanced .tab-item.activated .badge {
top: 2px;
opacity: 1; }
.tabs-striped.tabs-color-energized .tab-item {
color: rgba(255, 201, 0, 0.4);
opacity: 1; }
.tabs-striped.tabs-color-energized .tab-item .badge {
opacity: 0.4; }
.tabs-striped.tabs-color-energized .tab-item.tab-item-active, .tabs-striped.tabs-color-energized .tab-item.active, .tabs-striped.tabs-color-energized .tab-item.activated {
margin-top: -2px;
color: #ffc900;
border: 0 solid #ffc900;
border-top-width: 2px; }
.tabs-striped.tabs-color-energized .tab-item.tab-item-active .badge, .tabs-striped.tabs-color-energized .tab-item.active .badge, .tabs-striped.tabs-color-energized .tab-item.activated .badge {
top: 2px;
opacity: 1; }
.tabs-striped.tabs-color-royal .tab-item {
color: rgba(136, 106, 234, 0.4);
opacity: 1; }
.tabs-striped.tabs-color-royal .tab-item .badge {
opacity: 0.4; }
.tabs-striped.tabs-color-royal .tab-item.tab-item-active, .tabs-striped.tabs-color-royal .tab-item.active, .tabs-striped.tabs-color-royal .tab-item.activated {
margin-top: -2px;
color: #886aea;
border: 0 solid #886aea;
border-top-width: 2px; }
.tabs-striped.tabs-color-royal .tab-item.tab-item-active .badge, .tabs-striped.tabs-color-royal .tab-item.active .badge, .tabs-striped.tabs-color-royal .tab-item.activated .badge {
top: 2px;
opacity: 1; }
.tabs-striped.tabs-color-dark .tab-item {
color: rgba(68, 68, 68, 0.4);
opacity: 1; }
.tabs-striped.tabs-color-dark .tab-item .badge {
opacity: 0.4; }
.tabs-striped.tabs-color-dark .tab-item.tab-item-active, .tabs-striped.tabs-color-dark .tab-item.active, .tabs-striped.tabs-color-dark .tab-item.activated {
margin-top: -2px;
color: #444;
border: 0 solid #444;
border-top-width: 2px; }
.tabs-striped.tabs-color-dark .tab-item.tab-item-active .badge, .tabs-striped.tabs-color-dark .tab-item.active .badge, .tabs-striped.tabs-color-dark .tab-item.activated .badge {
top: 2px;
opacity: 1; }
.tabs-background-light .tabs,
.tabs-background-light > .tabs {
background-color: #fff;
background-image: linear-gradient(0deg, #ddd, #ddd 50%, transparent 50%);
border-color: #ddd; }
.tabs-background-stable .tabs,
.tabs-background-stable > .tabs {
background-color: #f8f8f8;
background-image: linear-gradient(0deg, #b2b2b2, #b2b2b2 50%, transparent 50%);
border-color: #b2b2b2; }
.tabs-background-positive .tabs,
.tabs-background-positive > .tabs {
background-color: #387ef5;
background-image: linear-gradient(0deg, #0c60ee, #0c60ee 50%, transparent 50%);
border-color: #0c60ee; }
.tabs-background-calm .tabs,
.tabs-background-calm > .tabs {
background-color: #11c1f3;
background-image: linear-gradient(0deg, #0a9dc7, #0a9dc7 50%, transparent 50%);
border-color: #0a9dc7; }
.tabs-background-assertive .tabs,
.tabs-background-assertive > .tabs {
background-color: #ef473a;
background-image: linear-gradient(0deg, #e42112, #e42112 50%, transparent 50%);
border-color: #e42112; }
.tabs-background-balanced .tabs,
.tabs-background-balanced > .tabs {
background-color: #33cd5f;
background-image: linear-gradient(0deg, #28a54c, #28a54c 50%, transparent 50%);
border-color: #28a54c; }
.tabs-background-energized .tabs,
.tabs-background-energized > .tabs {
background-color: #ffc900;
background-image: linear-gradient(0deg, #e6b500, #e6b500 50%, transparent 50%);
border-color: #e6b500; }
.tabs-background-royal .tabs,
.tabs-background-royal > .tabs {
background-color: #886aea;
background-image: linear-gradient(0deg, #6b46e5, #6b46e5 50%, transparent 50%);
border-color: #6b46e5; }
.tabs-background-dark .tabs,
.tabs-background-dark > .tabs {
background-color: #444;
background-image: linear-gradient(0deg, #111, #111 50%, transparent 50%);
border-color: #111; }
.tabs-color-light .tab-item {
color: rgba(255, 255, 255, 0.4);
opacity: 1; }
.tabs-color-light .tab-item .badge {
opacity: 0.4; }
.tabs-color-light .tab-item.tab-item-active, .tabs-color-light .tab-item.active, .tabs-color-light .tab-item.activated {
color: #fff;
border: 0 solid #fff; }
.tabs-color-light .tab-item.tab-item-active .badge, .tabs-color-light .tab-item.active .badge, .tabs-color-light .tab-item.activated .badge {
opacity: 1; }
.tabs-color-stable .tab-item {
color: rgba(248, 248, 248, 0.4);
opacity: 1; }
.tabs-color-stable .tab-item .badge {
opacity: 0.4; }
.tabs-color-stable .tab-item.tab-item-active, .tabs-color-stable .tab-item.active, .tabs-color-stable .tab-item.activated {
color: #f8f8f8;
border: 0 solid #f8f8f8; }
.tabs-color-stable .tab-item.tab-item-active .badge, .tabs-color-stable .tab-item.active .badge, .tabs-color-stable .tab-item.activated .badge {
opacity: 1; }
.tabs-color-positive .tab-item {
color: rgba(56, 126, 245, 0.4);
opacity: 1; }
.tabs-color-positive .tab-item .badge {
opacity: 0.4; }
.tabs-color-positive .tab-item.tab-item-active, .tabs-color-positive .tab-item.active, .tabs-color-positive .tab-item.activated {
color: #387ef5;
border: 0 solid #387ef5; }
.tabs-color-positive .tab-item.tab-item-active .badge, .tabs-color-positive .tab-item.active .badge, .tabs-color-positive .tab-item.activated .badge {
opacity: 1; }
.tabs-color-calm .tab-item {
color: rgba(17, 193, 243, 0.4);
opacity: 1; }
.tabs-color-calm .tab-item .badge {
opacity: 0.4; }
.tabs-color-calm .tab-item.tab-item-active, .tabs-color-calm .tab-item.active, .tabs-color-calm .tab-item.activated {
color: #11c1f3;
border: 0 solid #11c1f3; }
.tabs-color-calm .tab-item.tab-item-active .badge, .tabs-color-calm .tab-item.active .badge, .tabs-color-calm .tab-item.activated .badge {
opacity: 1; }
.tabs-color-assertive .tab-item {
color: rgba(239, 71, 58, 0.4);
opacity: 1; }
.tabs-color-assertive .tab-item .badge {
opacity: 0.4; }
.tabs-color-assertive .tab-item.tab-item-active, .tabs-color-assertive .tab-item.active, .tabs-color-assertive .tab-item.activated {
color: #ef473a;
border: 0 solid #ef473a; }
.tabs-color-assertive .tab-item.tab-item-active .badge, .tabs-color-assertive .tab-item.active .badge, .tabs-color-assertive .tab-item.activated .badge {
opacity: 1; }
.tabs-color-balanced .tab-item {
color: rgba(51, 205, 95, 0.4);
opacity: 1; }
.tabs-color-balanced .tab-item .badge {
opacity: 0.4; }
.tabs-color-balanced .tab-item.tab-item-active, .tabs-color-balanced .tab-item.active, .tabs-color-balanced .tab-item.activated {
color: #33cd5f;
border: 0 solid #33cd5f; }
.tabs-color-balanced .tab-item.tab-item-active .badge, .tabs-color-balanced .tab-item.active .badge, .tabs-color-balanced .tab-item.activated .badge {
opacity: 1; }
.tabs-color-energized .tab-item {
color: rgba(255, 201, 0, 0.4);
opacity: 1; }
.tabs-color-energized .tab-item .badge {
opacity: 0.4; }
.tabs-color-energized .tab-item.tab-item-active, .tabs-color-energized .tab-item.active, .tabs-color-energized .tab-item.activated {
color: #ffc900;
border: 0 solid #ffc900; }
.tabs-color-energized .tab-item.tab-item-active .badge, .tabs-color-energized .tab-item.active .badge, .tabs-color-energized .tab-item.activated .badge {
opacity: 1; }
.tabs-color-royal .tab-item {
color: rgba(136, 106, 234, 0.4);
opacity: 1; }
.tabs-color-royal .tab-item .badge {
opacity: 0.4; }
.tabs-color-royal .tab-item.tab-item-active, .tabs-color-royal .tab-item.active, .tabs-color-royal .tab-item.activated {
color: #886aea;
border: 0 solid #886aea; }
.tabs-color-royal .tab-item.tab-item-active .badge, .tabs-color-royal .tab-item.active .badge, .tabs-color-royal .tab-item.activated .badge {
opacity: 1; }
.tabs-color-dark .tab-item {
color: rgba(68, 68, 68, 0.4);
opacity: 1; }
.tabs-color-dark .tab-item .badge {
opacity: 0.4; }
.tabs-color-dark .tab-item.tab-item-active, .tabs-color-dark .tab-item.active, .tabs-color-dark .tab-item.activated {
color: #444;
border: 0 solid #444; }
.tabs-color-dark .tab-item.tab-item-active .badge, .tabs-color-dark .tab-item.active .badge, .tabs-color-dark .tab-item.activated .badge {
opacity: 1; }
ion-tabs.tabs-color-active-light .tab-item {
color: #444; }
ion-tabs.tabs-color-active-light .tab-item.tab-item-active, ion-tabs.tabs-color-active-light .tab-item.active, ion-tabs.tabs-color-active-light .tab-item.activated {
color: #fff; }
ion-tabs.tabs-color-active-stable .tab-item {
color: #444; }
ion-tabs.tabs-color-active-stable .tab-item.tab-item-active, ion-tabs.tabs-color-active-stable .tab-item.active, ion-tabs.tabs-color-active-stable .tab-item.activated {
color: #f8f8f8; }
ion-tabs.tabs-color-active-positive .tab-item {
color: #444; }
ion-tabs.tabs-color-active-positive .tab-item.tab-item-active, ion-tabs.tabs-color-active-positive .tab-item.active, ion-tabs.tabs-color-active-positive .tab-item.activated {
color: #387ef5; }
ion-tabs.tabs-color-active-calm .tab-item {
color: #444; }
ion-tabs.tabs-color-active-calm .tab-item.tab-item-active, ion-tabs.tabs-color-active-calm .tab-item.active, ion-tabs.tabs-color-active-calm .tab-item.activated {
color: #11c1f3; }
ion-tabs.tabs-color-active-assertive .tab-item {
color: #444; }
ion-tabs.tabs-color-active-assertive .tab-item.tab-item-active, ion-tabs.tabs-color-active-assertive .tab-item.active, ion-tabs.tabs-color-active-assertive .tab-item.activated {
color: #ef473a; }
ion-tabs.tabs-color-active-balanced .tab-item {
color: #444; }
ion-tabs.tabs-color-active-balanced .tab-item.tab-item-active, ion-tabs.tabs-color-active-balanced .tab-item.active, ion-tabs.tabs-color-active-balanced .tab-item.activated {
color: #33cd5f; }
ion-tabs.tabs-color-active-energized .tab-item {
color: #444; }
ion-tabs.tabs-color-active-energized .tab-item.tab-item-active, ion-tabs.tabs-color-active-energized .tab-item.active, ion-tabs.tabs-color-active-energized .tab-item.activated {
color: #ffc900; }
ion-tabs.tabs-color-active-royal .tab-item {
color: #444; }
ion-tabs.tabs-color-active-royal .tab-item.tab-item-active, ion-tabs.tabs-color-active-royal .tab-item.active, ion-tabs.tabs-color-active-royal .tab-item.activated {
color: #886aea; }
ion-tabs.tabs-color-active-dark .tab-item {
color: #fff; }
ion-tabs.tabs-color-active-dark .tab-item.tab-item-active, ion-tabs.tabs-color-active-dark .tab-item.active, ion-tabs.tabs-color-active-dark .tab-item.activated {
color: #444; }
.tabs-top.tabs-striped {
padding-bottom: 0; }
.tabs-top.tabs-striped .tab-item {
background: transparent;
-webkit-transition: color .1s ease;
-moz-transition: color .1s ease;
-ms-transition: color .1s ease;
-o-transition: color .1s ease;
transition: color .1s ease; }
.tabs-top.tabs-striped .tab-item.tab-item-active, .tabs-top.tabs-striped .tab-item.active, .tabs-top.tabs-striped .tab-item.activated {
margin-top: 1px;
border-width: 0px 0px 2px 0px !important;
border-style: solid; }
.tabs-top.tabs-striped .tab-item.tab-item-active > .badge, .tabs-top.tabs-striped .tab-item.tab-item-active > i, .tabs-top.tabs-striped .tab-item.active > .badge, .tabs-top.tabs-striped .tab-item.active > i, .tabs-top.tabs-striped .tab-item.activated > .badge, .tabs-top.tabs-striped .tab-item.activated > i {
margin-top: -1px; }
.tabs-top.tabs-striped .tab-item .badge {
-webkit-transition: color .2s ease;
-moz-transition: color .2s ease;
-ms-transition: color .2s ease;
-o-transition: color .2s ease;
transition: color .2s ease; }
.tabs-top.tabs-striped:not(.tabs-icon-left):not(.tabs-icon-top) .tab-item.tab-item-active .tab-title, .tabs-top.tabs-striped:not(.tabs-icon-left):not(.tabs-icon-top) .tab-item.tab-item-active i, .tabs-top.tabs-striped:not(.tabs-icon-left):not(.tabs-icon-top) .tab-item.active .tab-title, .tabs-top.tabs-striped:not(.tabs-icon-left):not(.tabs-icon-top) .tab-item.active i, .tabs-top.tabs-striped:not(.tabs-icon-left):not(.tabs-icon-top) .tab-item.activated .tab-title, .tabs-top.tabs-striped:not(.tabs-icon-left):not(.tabs-icon-top) .tab-item.activated i {
display: block;
margin-top: -1px; }
.tabs-top.tabs-striped.tabs-icon-left .tab-item {
margin-top: 1px; }
.tabs-top.tabs-striped.tabs-icon-left .tab-item.tab-item-active .tab-title, .tabs-top.tabs-striped.tabs-icon-left .tab-item.tab-item-active i, .tabs-top.tabs-striped.tabs-icon-left .tab-item.active .tab-title, .tabs-top.tabs-striped.tabs-icon-left .tab-item.active i, .tabs-top.tabs-striped.tabs-icon-left .tab-item.activated .tab-title, .tabs-top.tabs-striped.tabs-icon-left .tab-item.activated i {
margin-top: -0.1em; }
/* Allow parent element to have tabs-top */
/* If you change this, change platform.scss as well */
.tabs-top > .tabs,
.tabs.tabs-top {
top: 44px;
padding-top: 0;
background-position: bottom;
border-top-width: 0;
border-bottom-width: 1px; }
.tabs-top > .tabs .tab-item.tab-item-active .badge, .tabs-top > .tabs .tab-item.active .badge, .tabs-top > .tabs .tab-item.activated .badge,
.tabs.tabs-top .tab-item.tab-item-active .badge,
.tabs.tabs-top .tab-item.active .badge,
.tabs.tabs-top .tab-item.activated .badge {
top: 4%; }
.tabs-top ~ .bar-header {
border-bottom-width: 0; }
.tab-item {
-webkit-box-flex: 1;
-webkit-flex: 1;
-moz-box-flex: 1;
-moz-flex: 1;
-ms-flex: 1;
flex: 1;
display: block;
overflow: hidden;
max-width: 150px;
height: 100%;
color: inherit;
text-align: center;
text-decoration: none;
text-overflow: ellipsis;
white-space: nowrap;
font-weight: 400;
font-size: 14px;
font-family: "-apple-system", "Helvetica Neue", "Roboto", "Segoe UI", sans-serif;
opacity: 0.7; }
.tab-item:hover {
cursor: pointer; }
.tab-item.tab-hidden {
display: none; }
.tabs-item-hide > .tabs,
.tabs.tabs-item-hide {
display: none; }
.tabs-icon-top > .tabs .tab-item,
.tabs-icon-top.tabs .tab-item,
.tabs-icon-bottom > .tabs .tab-item,
.tabs-icon-bottom.tabs .tab-item {
font-size: 10px;
line-height: 14px; }
.tab-item .icon {
display: block;
margin: 0 auto;
height: 32px;
font-size: 32px; }
.tabs-icon-left.tabs .tab-item,
.tabs-icon-left > .tabs .tab-item,
.tabs-icon-right.tabs .tab-item,
.tabs-icon-right > .tabs .tab-item {
font-size: 10px; }
.tabs-icon-left.tabs .tab-item .icon, .tabs-icon-left.tabs .tab-item .tab-title,
.tabs-icon-left > .tabs .tab-item .icon,
.tabs-icon-left > .tabs .tab-item .tab-title,
.tabs-icon-right.tabs .tab-item .icon,
.tabs-icon-right.tabs .tab-item .tab-title,
.tabs-icon-right > .tabs .tab-item .icon,
.tabs-icon-right > .tabs .tab-item .tab-title {
display: inline-block;
vertical-align: top;
margin-top: -.1em; }
.tabs-icon-left.tabs .tab-item .icon:before, .tabs-icon-left.tabs .tab-item .tab-title:before,
.tabs-icon-left > .tabs .tab-item .icon:before,
.tabs-icon-left > .tabs .tab-item .tab-title:before,
.tabs-icon-right.tabs .tab-item .icon:before,
.tabs-icon-right.tabs .tab-item .tab-title:before,
.tabs-icon-right > .tabs .tab-item .icon:before,
.tabs-icon-right > .tabs .tab-item .tab-title:before {
font-size: 24px;
line-height: 49px; }
.tabs-icon-left > .tabs .tab-item .icon,
.tabs-icon-left.tabs .tab-item .icon {
padding-right: 3px; }
.tabs-icon-right > .tabs .tab-item .icon,
.tabs-icon-right.tabs .tab-item .icon {
padding-left: 3px; }
.tabs-icon-only > .tabs .icon,
.tabs-icon-only.tabs .icon {
line-height: inherit; }
.tab-item.has-badge {
position: relative; }
.tab-item .badge {
position: absolute;
top: 4%;
right: 33%;
right: calc(50% - 26px);
padding: 1px 6px;
height: auto;
font-size: 12px;
line-height: 16px; }
/* Navigational tab */
/* Active state for tab */
.tab-item.tab-item-active,
.tab-item.active,
.tab-item.activated {
opacity: 1; }
.tab-item.tab-item-active.tab-item-light,
.tab-item.active.tab-item-light,
.tab-item.activated.tab-item-light {
color: #fff; }
.tab-item.tab-item-active.tab-item-stable,
.tab-item.active.tab-item-stable,
.tab-item.activated.tab-item-stable {
color: #f8f8f8; }
.tab-item.tab-item-active.tab-item-positive,
.tab-item.active.tab-item-positive,
.tab-item.activated.tab-item-positive {
color: #387ef5; }
.tab-item.tab-item-active.tab-item-calm,
.tab-item.active.tab-item-calm,
.tab-item.activated.tab-item-calm {
color: #11c1f3; }
.tab-item.tab-item-active.tab-item-assertive,
.tab-item.active.tab-item-assertive,
.tab-item.activated.tab-item-assertive {
color: #ef473a; }
.tab-item.tab-item-active.tab-item-balanced,
.tab-item.active.tab-item-balanced,
.tab-item.activated.tab-item-balanced {
color: #33cd5f; }
.tab-item.tab-item-active.tab-item-energized,
.tab-item.active.tab-item-energized,
.tab-item.activated.tab-item-energized {
color: #ffc900; }
.tab-item.tab-item-active.tab-item-royal,
.tab-item.active.tab-item-royal,
.tab-item.activated.tab-item-royal {
color: #886aea; }
.tab-item.tab-item-active.tab-item-dark,
.tab-item.active.tab-item-dark,
.tab-item.activated.tab-item-dark {
color: #444; }
.item.tabs {
display: -webkit-box;
display: -webkit-flex;
display: -moz-box;
display: -moz-flex;
display: -ms-flexbox;
display: flex;
padding: 0; }
.item.tabs .icon:before {
position: relative; }
.tab-item.disabled,
.tab-item[disabled] {
opacity: .4;
cursor: default;
pointer-events: none; }
.nav-bar-tabs-top.hide ~ .view-container .tabs-top .tabs {
top: 0; }
.pane[hide-nav-bar="true"] .has-tabs-top {
top: 49px; }
/**
* Menus
* --------------------------------------------------
* Side panel structure
*/
.menu {
position: absolute;
top: 0;
bottom: 0;
z-index: 0;
overflow: hidden;
min-height: 100%;
max-height: 100%;
width: 275px;
background-color: #fff; }
.menu .scroll-content {
z-index: 10; }
.menu .bar-header {
z-index: 11; }
.menu-content {
-webkit-transform: none;
transform: none;
box-shadow: -1px 0px 2px rgba(0, 0, 0, 0.2), 1px 0px 2px rgba(0, 0, 0, 0.2); }
.menu-open .menu-content .pane,
.menu-open .menu-content .scroll-content {
pointer-events: none; }
.menu-open .menu-content .scroll-content .scroll {
pointer-events: none; }
.menu-open .menu-content .scroll-content:not(.overflow-scroll) {
overflow: hidden; }
.grade-b .menu-content,
.grade-c .menu-content {
-webkit-box-sizing: content-box;
-moz-box-sizing: content-box;
box-sizing: content-box;
right: -1px;
left: -1px;
border-right: 1px solid #ccc;
border-left: 1px solid #ccc;
box-shadow: none; }
.menu-left {
left: 0; }
.menu-right {
right: 0; }
.aside-open.aside-resizing .menu-right {
display: none; }
.menu-animated {
-webkit-transition: -webkit-transform 200ms ease;
transition: transform 200ms ease; }
/**
* Modals
* --------------------------------------------------
* Modals are independent windows that slide in from off-screen.
*/
.modal-backdrop,
.modal-backdrop-bg {
position: fixed;
top: 0;
left: 0;
z-index: 10;
width: 100%;
height: 100%; }
.modal-backdrop-bg {
pointer-events: none; }
.modal {
display: block;
position: absolute;
top: 0;
z-index: 10;
overflow: hidden;
min-height: 100%;
width: 100%;
background-color: #fff; }
@media (min-width: 680px) {
.modal {
top: 20%;
right: 20%;
bottom: 20%;
left: 20%;
min-height: 240px;
width: 60%; }
.modal.ng-leave-active {
bottom: 0; }
.platform-ios.platform-cordova .modal-wrapper .modal .bar-header:not(.bar-subheader) {
height: 44px; }
.platform-ios.platform-cordova .modal-wrapper .modal .bar-header:not(.bar-subheader) > * {
margin-top: 0; }
.platform-ios.platform-cordova .modal-wrapper .modal .tabs-top > .tabs,
.platform-ios.platform-cordova .modal-wrapper .modal .tabs.tabs-top {
top: 44px; }
.platform-ios.platform-cordova .modal-wrapper .modal .has-header,
.platform-ios.platform-cordova .modal-wrapper .modal .bar-subheader {
top: 44px; }
.platform-ios.platform-cordova .modal-wrapper .modal .has-subheader {
top: 88px; }
.platform-ios.platform-cordova .modal-wrapper .modal .has-header.has-tabs-top {
top: 93px; }
.platform-ios.platform-cordova .modal-wrapper .modal .has-header.has-subheader.has-tabs-top {
top: 137px; }
.modal-backdrop-bg {
-webkit-transition: opacity 300ms ease-in-out;
transition: opacity 300ms ease-in-out;
background-color: #000;
opacity: 0; }
.active .modal-backdrop-bg {
opacity: 0.5; } }
.modal-open {
pointer-events: none; }
.modal-open .modal,
.modal-open .modal-backdrop {
pointer-events: auto; }
.modal-open.loading-active .modal,
.modal-open.loading-active .modal-backdrop {
pointer-events: none; }
/**
* Popovers
* --------------------------------------------------
* Popovers are independent views which float over content
*/
.popover-backdrop {
position: fixed;
top: 0;
left: 0;
z-index: 10;
width: 100%;
height: 100%;
background-color: transparent; }
.popover-backdrop.active {
background-color: rgba(0, 0, 0, 0.1); }
.popover {
position: absolute;
top: 25%;
left: 50%;
z-index: 10;
display: block;
margin-top: 12px;
margin-left: -110px;
height: 280px;
width: 220px;
background-color: #fff;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
opacity: 0; }
.popover .item:first-child {
border-top: 0; }
.popover .item:last-child {
border-bottom: 0; }
.popover.popover-bottom {
margin-top: -12px; }
.popover,
.popover .bar-header {
border-radius: 2px; }
.popover .scroll-content {
z-index: 1;
margin: 2px 0; }
.popover .bar-header {
border-bottom-right-radius: 0;
border-bottom-left-radius: 0; }
.popover .has-header {
border-top-right-radius: 0;
border-top-left-radius: 0; }
.popover-arrow {
display: none; }
.platform-ios .popover {
box-shadow: 0 0 40px rgba(0, 0, 0, 0.08);
border-radius: 10px; }
.platform-ios .popover .bar-header {
-webkit-border-top-right-radius: 10px;
border-top-right-radius: 10px;
-webkit-border-top-left-radius: 10px;
border-top-left-radius: 10px; }
.platform-ios .popover .scroll-content {
margin: 8px 0;
border-radius: 10px; }
.platform-ios .popover .scroll-content.has-header {
margin-top: 0; }
.platform-ios .popover-arrow {
position: absolute;
display: block;
top: -17px;
width: 30px;
height: 19px;
overflow: hidden; }
.platform-ios .popover-arrow:after {
position: absolute;
top: 12px;
left: 5px;
width: 20px;
height: 20px;
background-color: #fff;
border-radius: 3px;
content: '';
-webkit-transform: rotate(-45deg);
transform: rotate(-45deg); }
.platform-ios .popover-bottom .popover-arrow {
top: auto;
bottom: -10px; }
.platform-ios .popover-bottom .popover-arrow:after {
top: -6px; }
.platform-android .popover {
margin-top: -32px;
background-color: #fafafa;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.35); }
.platform-android .popover .item {
border-color: #fafafa;
background-color: #fafafa;
color: #4d4d4d; }
.platform-android .popover.popover-bottom {
margin-top: 32px; }
.platform-android .popover-backdrop,
.platform-android .popover-backdrop.active {
background-color: transparent; }
.popover-open {
pointer-events: none; }
.popover-open .popover,
.popover-open .popover-backdrop {
pointer-events: auto; }
.popover-open.loading-active .popover,
.popover-open.loading-active .popover-backdrop {
pointer-events: none; }
@media (min-width: 680px) {
.popover {
width: 360px;
margin-left: -180px; } }
/**
* Popups
* --------------------------------------------------
*/
.popup-container {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
background: transparent;
display: -webkit-box;
display: -webkit-flex;
display: -moz-box;
display: -moz-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
-webkit-justify-content: center;
-moz-justify-content: center;
justify-content: center;
-webkit-box-align: center;
-ms-flex-align: center;
-webkit-align-items: center;
-moz-align-items: center;
align-items: center;
z-index: 12;
visibility: hidden; }
.popup-container.popup-showing {
visibility: visible; }
.popup-container.popup-hidden .popup {
-webkit-animation-name: scaleOut;
animation-name: scaleOut;
-webkit-animation-duration: 0.1s;
animation-duration: 0.1s;
-webkit-animation-timing-function: ease-in-out;
animation-timing-function: ease-in-out;
-webkit-animation-fill-mode: both;
animation-fill-mode: both; }
.popup-container.active .popup {
-webkit-animation-name: superScaleIn;
animation-name: superScaleIn;
-webkit-animation-duration: 0.2s;
animation-duration: 0.2s;
-webkit-animation-timing-function: ease-in-out;
animation-timing-function: ease-in-out;
-webkit-animation-fill-mode: both;
animation-fill-mode: both; }
.popup-container .popup {
width: 250px;
max-width: 100%;
max-height: 90%;
border-radius: 0px;
background-color: rgba(255, 255, 255, 0.9);
display: -webkit-box;
display: -webkit-flex;
display: -moz-box;
display: -moz-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-direction: normal;
-webkit-box-orient: vertical;
-webkit-flex-direction: column;
-moz-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column; }
.popup-container input,
.popup-container textarea {
width: 100%; }
.popup-head {
padding: 15px 10px;
border-bottom: 1px solid #eee;
text-align: center; }
.popup-title {
margin: 0;
padding: 0;
font-size: 15px; }
.popup-sub-title {
margin: 5px 0 0 0;
padding: 0;
font-weight: normal;
font-size: 11px; }
.popup-body {
padding: 10px;
overflow: auto; }
.popup-buttons {
display: -webkit-box;
display: -webkit-flex;
display: -moz-box;
display: -moz-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-direction: normal;
-webkit-box-orient: horizontal;
-webkit-flex-direction: row;
-moz-flex-direction: row;
-ms-flex-direction: row;
flex-direction: row;
padding: 10px;
min-height: 65px; }
.popup-buttons .button {
-webkit-box-flex: 1;
-webkit-flex: 1;
-moz-box-flex: 1;
-moz-flex: 1;
-ms-flex: 1;
flex: 1;
display: block;
min-height: 45px;
border-radius: 2px;
line-height: 20px;
margin-right: 5px; }
.popup-buttons .button:last-child {
margin-right: 0px; }
.popup-open {
pointer-events: none; }
.popup-open.modal-open .modal {
pointer-events: none; }
.popup-open .popup-backdrop, .popup-open .popup {
pointer-events: auto; }
/**
* Loading
* --------------------------------------------------
*/
.loading-container {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
z-index: 13;
display: -webkit-box;
display: -webkit-flex;
display: -moz-box;
display: -moz-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
-webkit-justify-content: center;
-moz-justify-content: center;
justify-content: center;
-webkit-box-align: center;
-ms-flex-align: center;
-webkit-align-items: center;
-moz-align-items: center;
align-items: center;
-webkit-transition: 0.2s opacity linear;
transition: 0.2s opacity linear;
visibility: hidden;
opacity: 0; }
.loading-container:not(.visible) .icon,
.loading-container:not(.visible) .spinner {
display: none; }
.loading-container.visible {
visibility: visible; }
.loading-container.active {
opacity: 1; }
.loading-container .loading {
padding: 20px;
border-radius: 5px;
background-color: rgba(0, 0, 0, 0.7);
color: #fff;
text-align: center;
text-overflow: ellipsis;
font-size: 15px; }
.loading-container .loading h1, .loading-container .loading h2, .loading-container .loading h3, .loading-container .loading h4, .loading-container .loading h5, .loading-container .loading h6 {
color: #fff; }
/**
* Items
* --------------------------------------------------
*/
.item {
border-color: #ddd;
background-color: #fff;
color: #444;
position: relative;
z-index: 2;
display: block;
margin: -1px;
padding: 16px;
border-width: 1px;
border-style: solid;
font-size: 16px; }
.item h2 {
margin: 0 0 2px 0;
font-size: 16px;
font-weight: normal; }
.item h3 {
margin: 0 0 4px 0;
font-size: 14px; }
.item h4 {
margin: 0 0 4px 0;
font-size: 12px; }
.item h5, .item h6 {
margin: 0 0 3px 0;
font-size: 10px; }
.item p {
color: #666;
font-size: 14px;
margin-bottom: 2px; }
.item h1:last-child,
.item h2:last-child,
.item h3:last-child,
.item h4:last-child,
.item h5:last-child,
.item h6:last-child,
.item p:last-child {
margin-bottom: 0; }
.item .badge {
display: -webkit-box;
display: -webkit-flex;
display: -moz-box;
display: -moz-flex;
display: -ms-flexbox;
display: flex;
position: absolute;
top: 16px;
right: 32px; }
.item.item-button-right .badge {
right: 67px; }
.item.item-divider .badge {
top: 8px; }
.item .badge + .badge {
margin-right: 5px; }
.item.item-light {
border-color: #ddd;
background-color: #fff;
color: #444; }
.item.item-stable {
border-color: #b2b2b2;
background-color: #f8f8f8;
color: #444; }
.item.item-positive {
border-color: #0c60ee;
background-color: #387ef5;
color: #fff; }
.item.item-calm {
border-color: #0a9dc7;
background-color: #11c1f3;
color: #fff; }
.item.item-assertive {
border-color: #e42112;
background-color: #ef473a;
color: #fff; }
.item.item-balanced {
border-color: #28a54c;
background-color: #33cd5f;
color: #fff; }
.item.item-energized {
border-color: #e6b500;
background-color: #ffc900;
color: #fff; }
.item.item-royal {
border-color: #6b46e5;
background-color: #886aea;
color: #fff; }
.item.item-dark {
border-color: #111;
background-color: #444;
color: #fff; }
.item[ng-click]:hover {
cursor: pointer; }
.list-borderless .item,
.item-borderless {
border-width: 0; }
.item.active,
.item.activated,
.item-complex.active .item-content,
.item-complex.activated .item-content,
.item .item-content.active,
.item .item-content.activated {
border-color: #ccc;
background-color: #D9D9D9; }
.item.active.item-complex > .item-content,
.item.activated.item-complex > .item-content,
.item-complex.active .item-content.item-complex > .item-content,
.item-complex.activated .item-content.item-complex > .item-content,
.item .item-content.active.item-complex > .item-content,
.item .item-content.activated.item-complex > .item-content {
border-color: #ccc;
background-color: #D9D9D9; }
.item.active.item-light,
.item.activated.item-light,
.item-complex.active .item-content.item-light,
.item-complex.activated .item-content.item-light,
.item .item-content.active.item-light,
.item .item-content.activated.item-light {
border-color: #ccc;
background-color: #fafafa; }
.item.active.item-light.item-complex > .item-content,
.item.activated.item-light.item-complex > .item-content,
.item-complex.active .item-content.item-light.item-complex > .item-content,
.item-complex.activated .item-content.item-light.item-complex > .item-content,
.item .item-content.active.item-light.item-complex > .item-content,
.item .item-content.activated.item-light.item-complex > .item-content {
border-color: #ccc;
background-color: #fafafa; }
.item.active.item-stable,
.item.activated.item-stable,
.item-complex.active .item-content.item-stable,
.item-complex.activated .item-content.item-stable,
.item .item-content.active.item-stable,
.item .item-content.activated.item-stable {
border-color: #a2a2a2;
background-color: #e5e5e5; }
.item.active.item-stable.item-complex > .item-content,
.item.activated.item-stable.item-complex > .item-content,
.item-complex.active .item-content.item-stable.item-complex > .item-content,
.item-complex.activated .item-content.item-stable.item-complex > .item-content,
.item .item-content.active.item-stable.item-complex > .item-content,
.item .item-content.activated.item-stable.item-complex > .item-content {
border-color: #a2a2a2;
background-color: #e5e5e5; }
.item.active.item-positive,
.item.activated.item-positive,
.item-complex.active .item-content.item-positive,
.item-complex.activated .item-content.item-positive,
.item .item-content.active.item-positive,
.item .item-content.activated.item-positive {
border-color: #0c60ee;
background-color: #0c60ee; }
.item.active.item-positive.item-complex > .item-content,
.item.activated.item-positive.item-complex > .item-content,
.item-complex.active .item-content.item-positive.item-complex > .item-content,
.item-complex.activated .item-content.item-positive.item-complex > .item-content,
.item .item-content.active.item-positive.item-complex > .item-content,
.item .item-content.activated.item-positive.item-complex > .item-content {
border-color: #0c60ee;
background-color: #0c60ee; }
.item.active.item-calm,
.item.activated.item-calm,
.item-complex.active .item-content.item-calm,
.item-complex.activated .item-content.item-calm,
.item .item-content.active.item-calm,
.item .item-content.activated.item-calm {
border-color: #0a9dc7;
background-color: #0a9dc7; }
.item.active.item-calm.item-complex > .item-content,
.item.activated.item-calm.item-complex > .item-content,
.item-complex.active .item-content.item-calm.item-complex > .item-content,
.item-complex.activated .item-content.item-calm.item-complex > .item-content,
.item .item-content.active.item-calm.item-complex > .item-content,
.item .item-content.activated.item-calm.item-complex > .item-content {
border-color: #0a9dc7;
background-color: #0a9dc7; }
.item.active.item-assertive,
.item.activated.item-assertive,
.item-complex.active .item-content.item-assertive,
.item-complex.activated .item-content.item-assertive,
.item .item-content.active.item-assertive,
.item .item-content.activated.item-assertive {
border-color: #e42112;
background-color: #e42112; }
.item.active.item-assertive.item-complex > .item-content,
.item.activated.item-assertive.item-complex > .item-content,
.item-complex.active .item-content.item-assertive.item-complex > .item-content,
.item-complex.activated .item-content.item-assertive.item-complex > .item-content,
.item .item-content.active.item-assertive.item-complex > .item-content,
.item .item-content.activated.item-assertive.item-complex > .item-content {
border-color: #e42112;
background-color: #e42112; }
.item.active.item-balanced,
.item.activated.item-balanced,
.item-complex.active .item-content.item-balanced,
.item-complex.activated .item-content.item-balanced,
.item .item-content.active.item-balanced,
.item .item-content.activated.item-balanced {
border-color: #28a54c;
background-color: #28a54c; }
.item.active.item-balanced.item-complex > .item-content,
.item.activated.item-balanced.item-complex > .item-content,
.item-complex.active .item-content.item-balanced.item-complex > .item-content,
.item-complex.activated .item-content.item-balanced.item-complex > .item-content,
.item .item-content.active.item-balanced.item-complex > .item-content,
.item .item-content.activated.item-balanced.item-complex > .item-content {
border-color: #28a54c;
background-color: #28a54c; }
.item.active.item-energized,
.item.activated.item-energized,
.item-complex.active .item-content.item-energized,
.item-complex.activated .item-content.item-energized,
.item .item-content.active.item-energized,
.item .item-content.activated.item-energized {
border-color: #e6b500;
background-color: #e6b500; }
.item.active.item-energized.item-complex > .item-content,
.item.activated.item-energized.item-complex > .item-content,
.item-complex.active .item-content.item-energized.item-complex > .item-content,
.item-complex.activated .item-content.item-energized.item-complex > .item-content,
.item .item-content.active.item-energized.item-complex > .item-content,
.item .item-content.activated.item-energized.item-complex > .item-content {
border-color: #e6b500;
background-color: #e6b500; }
.item.active.item-royal,
.item.activated.item-royal,
.item-complex.active .item-content.item-royal,
.item-complex.activated .item-content.item-royal,
.item .item-content.active.item-royal,
.item .item-content.activated.item-royal {
border-color: #6b46e5;
background-color: #6b46e5; }
.item.active.item-royal.item-complex > .item-content,
.item.activated.item-royal.item-complex > .item-content,
.item-complex.active .item-content.item-royal.item-complex > .item-content,
.item-complex.activated .item-content.item-royal.item-complex > .item-content,
.item .item-content.active.item-royal.item-complex > .item-content,
.item .item-content.activated.item-royal.item-complex > .item-content {
border-color: #6b46e5;
background-color: #6b46e5; }
.item.active.item-dark,
.item.activated.item-dark,
.item-complex.active .item-content.item-dark,
.item-complex.activated .item-content.item-dark,
.item .item-content.active.item-dark,
.item .item-content.activated.item-dark {
border-color: #000;
background-color: #262626; }
.item.active.item-dark.item-complex > .item-content,
.item.activated.item-dark.item-complex > .item-content,
.item-complex.active .item-content.item-dark.item-complex > .item-content,
.item-complex.activated .item-content.item-dark.item-complex > .item-content,
.item .item-content.active.item-dark.item-complex > .item-content,
.item .item-content.activated.item-dark.item-complex > .item-content {
border-color: #000;
background-color: #262626; }
.item,
.item h1,
.item h2,
.item h3,
.item h4,
.item h5,
.item h6,
.item p,
.item-content,
.item-content h1,
.item-content h2,
.item-content h3,
.item-content h4,
.item-content h5,
.item-content h6,
.item-content p {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap; }
a.item {
color: inherit;
text-decoration: none; }
a.item:hover, a.item:focus {
text-decoration: none; }
/**
* Complex Items
* --------------------------------------------------
* Adding .item-complex allows the .item to be slidable and
* have options underneath the button, but also requires an
* additional .item-content element inside .item.
* Basically .item-complex removes any default settings which
* .item added, so that .item-content looks them as just .item.
*/
.item-complex,
a.item.item-complex,
button.item.item-complex {
padding: 0; }
.item-complex .item-content,
.item-radio .item-content {
position: relative;
z-index: 2;
padding: 16px 49px 16px 16px;
border: none;
background-color: #fff; }
a.item-content {
display: block;
color: inherit;
text-decoration: none; }
.item-text-wrap .item,
.item-text-wrap .item-content,
.item-text-wrap,
.item-text-wrap h1,
.item-text-wrap h2,
.item-text-wrap h3,
.item-text-wrap h4,
.item-text-wrap h5,
.item-text-wrap h6,
.item-text-wrap p,
.item-complex.item-text-wrap .item-content,
.item-body h1,
.item-body h2,
.item-body h3,
.item-body h4,
.item-body h5,
.item-body h6,
.item-body p {
overflow: visible;
white-space: normal; }
.item-complex.item-text-wrap,
.item-complex.item-text-wrap h1,
.item-complex.item-text-wrap h2,
.item-complex.item-text-wrap h3,
.item-complex.item-text-wrap h4,
.item-complex.item-text-wrap h5,
.item-complex.item-text-wrap h6,
.item-complex.item-text-wrap p {
overflow: visible;
white-space: normal; }
.item-complex.item-light > .item-content {
border-color: #ddd;
background-color: #fff;
color: #444; }
.item-complex.item-light > .item-content.active, .item-complex.item-light > .item-content:active {
border-color: #ccc;
background-color: #fafafa; }
.item-complex.item-light > .item-content.active.item-complex > .item-content, .item-complex.item-light > .item-content:active.item-complex > .item-content {
border-color: #ccc;
background-color: #fafafa; }
.item-complex.item-stable > .item-content {
border-color: #b2b2b2;
background-color: #f8f8f8;
color: #444; }
.item-complex.item-stable > .item-content.active, .item-complex.item-stable > .item-content:active {
border-color: #a2a2a2;
background-color: #e5e5e5; }
.item-complex.item-stable > .item-content.active.item-complex > .item-content, .item-complex.item-stable > .item-content:active.item-complex > .item-content {
border-color: #a2a2a2;
background-color: #e5e5e5; }
.item-complex.item-positive > .item-content {
border-color: #0c60ee;
background-color: #387ef5;
color: #fff; }
.item-complex.item-positive > .item-content.active, .item-complex.item-positive > .item-content:active {
border-color: #0c60ee;
background-color: #0c60ee; }
.item-complex.item-positive > .item-content.active.item-complex > .item-content, .item-complex.item-positive > .item-content:active.item-complex > .item-content {
border-color: #0c60ee;
background-color: #0c60ee; }
.item-complex.item-calm > .item-content {
border-color: #0a9dc7;
background-color: #11c1f3;
color: #fff; }
.item-complex.item-calm > .item-content.active, .item-complex.item-calm > .item-content:active {
border-color: #0a9dc7;
background-color: #0a9dc7; }
.item-complex.item-calm > .item-content.active.item-complex > .item-content, .item-complex.item-calm > .item-content:active.item-complex > .item-content {
border-color: #0a9dc7;
background-color: #0a9dc7; }
.item-complex.item-assertive > .item-content {
border-color: #e42112;
background-color: #ef473a;
color: #fff; }
.item-complex.item-assertive > .item-content.active, .item-complex.item-assertive > .item-content:active {
border-color: #e42112;
background-color: #e42112; }
.item-complex.item-assertive > .item-content.active.item-complex > .item-content, .item-complex.item-assertive > .item-content:active.item-complex > .item-content {
border-color: #e42112;
background-color: #e42112; }
.item-complex.item-balanced > .item-content {
border-color: #28a54c;
background-color: #33cd5f;
color: #fff; }
.item-complex.item-balanced > .item-content.active, .item-complex.item-balanced > .item-content:active {
border-color: #28a54c;
background-color: #28a54c; }
.item-complex.item-balanced > .item-content.active.item-complex > .item-content, .item-complex.item-balanced > .item-content:active.item-complex > .item-content {
border-color: #28a54c;
background-color: #28a54c; }
.item-complex.item-energized > .item-content {
border-color: #e6b500;
background-color: #ffc900;
color: #fff; }
.item-complex.item-energized > .item-content.active, .item-complex.item-energized > .item-content:active {
border-color: #e6b500;
background-color: #e6b500; }
.item-complex.item-energized > .item-content.active.item-complex > .item-content, .item-complex.item-energized > .item-content:active.item-complex > .item-content {
border-color: #e6b500;
background-color: #e6b500; }
.item-complex.item-royal > .item-content {
border-color: #6b46e5;
background-color: #886aea;
color: #fff; }
.item-complex.item-royal > .item-content.active, .item-complex.item-royal > .item-content:active {
border-color: #6b46e5;
background-color: #6b46e5; }
.item-complex.item-royal > .item-content.active.item-complex > .item-content, .item-complex.item-royal > .item-content:active.item-complex > .item-content {
border-color: #6b46e5;
background-color: #6b46e5; }
.item-complex.item-dark > .item-content {
border-color: #111;
background-color: #444;
color: #fff; }
.item-complex.item-dark > .item-content.active, .item-complex.item-dark > .item-content:active {
border-color: #000;
background-color: #262626; }
.item-complex.item-dark > .item-content.active.item-complex > .item-content, .item-complex.item-dark > .item-content:active.item-complex > .item-content {
border-color: #000;
background-color: #262626; }
/**
* Item Icons
* --------------------------------------------------
*/
.item-icon-left .icon,
.item-icon-right .icon {
display: -webkit-box;
display: -webkit-flex;
display: -moz-box;
display: -moz-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
-webkit-align-items: center;
-moz-align-items: center;
align-items: center;
position: absolute;
top: 0;
height: 100%;
font-size: 32px; }
.item-icon-left .icon:before,
.item-icon-right .icon:before {
display: block;
width: 32px;
text-align: center; }
.item .fill-icon {
min-width: 30px;
min-height: 30px;
font-size: 28px; }
.item-icon-left {
padding-left: 54px; }
.item-icon-left .icon {
left: 11px; }
.item-complex.item-icon-left {
padding-left: 0; }
.item-complex.item-icon-left .item-content {
padding-left: 54px; }
.item-icon-right {
padding-right: 54px; }
.item-icon-right .icon {
right: 11px; }
.item-complex.item-icon-right {
padding-right: 0; }
.item-complex.item-icon-right .item-content {
padding-right: 54px; }
.item-icon-left.item-icon-right .icon:first-child {
right: auto; }
.item-icon-left.item-icon-right .icon:last-child,
.item-icon-left .item-delete .icon {
left: auto; }
.item-icon-left .icon-accessory,
.item-icon-right .icon-accessory {
color: #ccc;
font-size: 16px; }
.item-icon-left .icon-accessory {
left: 3px; }
.item-icon-right .icon-accessory {
right: 3px; }
/**
* Item Button
* --------------------------------------------------
* An item button is a child button inside an .item (not the entire .item)
*/
.item-button-left {
padding-left: 72px; }
.item-button-left > .button,
.item-button-left .item-content > .button {
display: -webkit-box;
display: -webkit-flex;
display: -moz-box;
display: -moz-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
-webkit-align-items: center;
-moz-align-items: center;
align-items: center;
position: absolute;
top: 8px;
left: 11px;
min-width: 34px;
min-height: 34px;
font-size: 18px;
line-height: 32px; }
.item-button-left > .button .icon:before,
.item-button-left .item-content > .button .icon:before {
position: relative;
left: auto;
width: auto;
line-height: 31px; }
.item-button-left > .button > .button,
.item-button-left .item-content > .button > .button {
margin: 0px 2px;
min-height: 34px;
font-size: 18px;
line-height: 32px; }
.item-button-right,
a.item.item-button-right,
button.item.item-button-right {
padding-right: 80px; }
.item-button-right > .button,
.item-button-right .item-content > .button,
.item-button-right > .buttons,
.item-button-right .item-content > .buttons {
display: -webkit-box;
display: -webkit-flex;
display: -moz-box;
display: -moz-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
-webkit-align-items: center;
-moz-align-items: center;
align-items: center;
position: absolute;
top: 8px;
right: 16px;
min-width: 34px;
min-height: 34px;
font-size: 18px;
line-height: 32px; }
.item-button-right > .button .icon:before,
.item-button-right .item-content > .button .icon:before,
.item-button-right > .buttons .icon:before,
.item-button-right .item-content > .buttons .icon:before {
position: relative;
left: auto;
width: auto;
line-height: 31px; }
.item-button-right > .button > .button,
.item-button-right .item-content > .button > .button,
.item-button-right > .buttons > .button,
.item-button-right .item-content > .buttons > .button {
margin: 0px 2px;
min-width: 34px;
min-height: 34px;
font-size: 18px;
line-height: 32px; }
.item-avatar,
.item-avatar .item-content,
.item-avatar-left,
.item-avatar-left .item-content {
padding-left: 72px;
min-height: 72px; }
.item-avatar > img:first-child,
.item-avatar .item-image,
.item-avatar .item-content > img:first-child,
.item-avatar .item-content .item-image,
.item-avatar-left > img:first-child,
.item-avatar-left .item-image,
.item-avatar-left .item-content > img:first-child,
.item-avatar-left .item-content .item-image {
position: absolute;
top: 16px;
left: 16px;
max-width: 40px;
max-height: 40px;
width: 100%;
height: 100%;
border-radius: 50%; }
.item-avatar-right,
.item-avatar-right .item-content {
padding-right: 72px;
min-height: 72px; }
.item-avatar-right > img:first-child,
.item-avatar-right .item-image,
.item-avatar-right .item-content > img:first-child,
.item-avatar-right .item-content .item-image {
position: absolute;
top: 16px;
right: 16px;
max-width: 40px;
max-height: 40px;
width: 100%;
height: 100%;
border-radius: 50%; }
.item-thumbnail-left,
.item-thumbnail-left .item-content {
padding-top: 8px;
padding-left: 106px;
min-height: 100px; }
.item-thumbnail-left > img:first-child,
.item-thumbnail-left .item-image,
.item-thumbnail-left .item-content > img:first-child,
.item-thumbnail-left .item-content .item-image {
position: absolute;
top: 10px;
left: 10px;
max-width: 80px;
max-height: 80px;
width: 100%;
height: 100%; }
.item-avatar.item-complex,
.item-avatar-left.item-complex,
.item-thumbnail-left.item-complex {
padding-top: 0;
padding-left: 0; }
.item-thumbnail-right,
.item-thumbnail-right .item-content {
padding-top: 8px;
padding-right: 106px;
min-height: 100px; }
.item-thumbnail-right > img:first-child,
.item-thumbnail-right .item-image,
.item-thumbnail-right .item-content > img:first-child,
.item-thumbnail-right .item-content .item-image {
position: absolute;
top: 10px;
right: 10px;
max-width: 80px;
max-height: 80px;
width: 100%;
height: 100%; }
.item-avatar-right.item-complex,
.item-thumbnail-right.item-complex {
padding-top: 0;
padding-right: 0; }
.item-image {
padding: 0;
text-align: center; }
.item-image img:first-child, .item-image .list-img {
width: 100%;
vertical-align: middle; }
.item-body {
overflow: auto;
padding: 16px;
text-overflow: inherit;
white-space: normal; }
.item-body h1, .item-body h2, .item-body h3, .item-body h4, .item-body h5, .item-body h6, .item-body p {
margin-top: 16px;
margin-bottom: 16px; }
.item-divider {
padding-top: 8px;
padding-bottom: 8px;
min-height: 30px;
background-color: #f5f5f5;
color: #222;
font-weight: 500; }
.platform-ios .item-divider-platform,
.item-divider-ios {
padding-top: 26px;
text-transform: uppercase;
font-weight: 300;
font-size: 13px;
background-color: #efeff4;
color: #555; }
.platform-android .item-divider-platform,
.item-divider-android {
font-weight: 300;
font-size: 13px; }
.item-note {
float: right;
color: #aaa;
font-size: 14px; }
.item-left-editable .item-content,
.item-right-editable .item-content {
-webkit-transition-duration: 250ms;
transition-duration: 250ms;
-webkit-transition-timing-function: ease-in-out;
transition-timing-function: ease-in-out;
-webkit-transition-property: -webkit-transform;
-moz-transition-property: -moz-transform;
transition-property: transform; }
.list-left-editing .item-left-editable .item-content,
.item-left-editing.item-left-editable .item-content {
-webkit-transform: translate3d(50px, 0, 0);
transform: translate3d(50px, 0, 0); }
.item-remove-animate.ng-leave {
-webkit-transition-duration: 300ms;
transition-duration: 300ms; }
.item-remove-animate.ng-leave .item-content, .item-remove-animate.ng-leave:last-of-type {
-webkit-transition-duration: 300ms;
transition-duration: 300ms;
-webkit-transition-timing-function: ease-in;
transition-timing-function: ease-in;
-webkit-transition-property: all;
transition-property: all; }
.item-remove-animate.ng-leave.ng-leave-active .item-content {
opacity: 0;
-webkit-transform: translate3d(-100%, 0, 0) !important;
transform: translate3d(-100%, 0, 0) !important; }
.item-remove-animate.ng-leave.ng-leave-active:last-of-type {
opacity: 0; }
.item-remove-animate.ng-leave.ng-leave-active ~ ion-item:not(.ng-leave) {
-webkit-transform: translate3d(0, -webkit-calc(-100% + 1px), 0);
transform: translate3d(0, calc(-100% + 1px), 0);
-webkit-transition-duration: 300ms;
transition-duration: 300ms;
-webkit-transition-timing-function: cubic-bezier(0.25, 0.81, 0.24, 1);
transition-timing-function: cubic-bezier(0.25, 0.81, 0.24, 1);
-webkit-transition-property: all;
transition-property: all; }
.item-left-edit {
-webkit-transition: all ease-in-out 125ms;
transition: all ease-in-out 125ms;
position: absolute;
top: 0;
left: 0;
z-index: 0;
width: 50px;
height: 100%;
line-height: 100%;
display: none;
opacity: 0;
-webkit-transform: translate3d(-21px, 0, 0);
transform: translate3d(-21px, 0, 0); }
.item-left-edit .button {
height: 100%; }
.item-left-edit .button.icon {
display: -webkit-box;
display: -webkit-flex;
display: -moz-box;
display: -moz-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
-webkit-align-items: center;
-moz-align-items: center;
align-items: center;
position: absolute;
top: 0;
height: 100%; }
.item-left-edit.visible {
display: block; }
.item-left-edit.visible.active {
opacity: 1;
-webkit-transform: translate3d(8px, 0, 0);
transform: translate3d(8px, 0, 0); }
.list-left-editing .item-left-edit {
-webkit-transition-delay: 125ms;
transition-delay: 125ms; }
.item-delete .button.icon {
color: #ef473a;
font-size: 24px; }
.item-delete .button.icon:hover {
opacity: .7; }
.item-right-edit {
-webkit-transition: all ease-in-out 250ms;
transition: all ease-in-out 250ms;
position: absolute;
top: 0;
right: 0;
z-index: 3;
width: 75px;
height: 100%;
background: inherit;
padding-left: 20px;
display: block;
opacity: 0;
-webkit-transform: translate3d(75px, 0, 0);
transform: translate3d(75px, 0, 0); }
.item-right-edit .button {
min-width: 50px;
height: 100%; }
.item-right-edit .button.icon {
display: -webkit-box;
display: -webkit-flex;
display: -moz-box;
display: -moz-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
-webkit-align-items: center;
-moz-align-items: center;
align-items: center;
position: absolute;
top: 0;
height: 100%;
font-size: 32px; }
.item-right-edit.visible {
display: block; }
.item-right-edit.visible.active {
opacity: 1;
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0); }
.item-reorder .button.icon {
color: #444;
font-size: 32px; }
.item-reordering {
position: absolute;
left: 0;
top: 0;
z-index: 9;
width: 100%;
box-shadow: 0px 0px 10px 0px #aaa; }
.item-reordering .item-reorder {
z-index: 9; }
.item-placeholder {
opacity: 0.7; }
/**
* The hidden right-side buttons that can be exposed under a list item
* with dragging.
*/
.item-options {
position: absolute;
top: 0;
right: 0;
z-index: 1;
height: 100%; }
.item-options .button {
height: 100%;
border: none;
border-radius: 0;
display: -webkit-inline-box;
display: -webkit-inline-flex;
display: -moz-inline-flex;
display: -ms-inline-flexbox;
display: inline-flex;
-webkit-box-align: center;
-ms-flex-align: center;
-webkit-align-items: center;
-moz-align-items: center;
align-items: center; }
.item-options .button:before {
margin: 0 auto; }
/**
* Lists
* --------------------------------------------------
*/
.list {
position: relative;
padding-top: 1px;
padding-bottom: 1px;
padding-left: 0;
margin-bottom: 20px; }
.list:last-child {
margin-bottom: 0px; }
.list:last-child.card {
margin-bottom: 40px; }
/**
* List Header
* --------------------------------------------------
*/
.list-header {
margin-top: 20px;
padding: 5px 15px;
background-color: transparent;
color: #222;
font-weight: bold; }
.card.list .list-item {
padding-right: 1px;
padding-left: 1px; }
/**
* Cards and Inset Lists
* --------------------------------------------------
* A card and list-inset are close to the same thing, except a card as a box shadow.
*/
.card,
.list-inset {
overflow: hidden;
margin: 20px 10px;
border-radius: 2px;
background-color: #fff; }
.card {
padding-top: 1px;
padding-bottom: 1px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); }
.card .item {
border-left: 0;
border-right: 0; }
.card .item:first-child {
border-top: 0; }
.card .item:last-child {
border-bottom: 0; }
.padding .card, .padding .list-inset {
margin-left: 0;
margin-right: 0; }
.card .item:first-child,
.list-inset .item:first-child,
.padding > .list .item:first-child {
border-top-left-radius: 2px;
border-top-right-radius: 2px; }
.card .item:first-child .item-content,
.list-inset .item:first-child .item-content,
.padding > .list .item:first-child .item-content {
border-top-left-radius: 2px;
border-top-right-radius: 2px; }
.card .item:last-child,
.list-inset .item:last-child,
.padding > .list .item:last-child {
border-bottom-right-radius: 2px;
border-bottom-left-radius: 2px; }
.card .item:last-child .item-content,
.list-inset .item:last-child .item-content,
.padding > .list .item:last-child .item-content {
border-bottom-right-radius: 2px;
border-bottom-left-radius: 2px; }
.card .item:last-child,
.list-inset .item:last-child {
margin-bottom: -1px; }
.card .item,
.list-inset .item,
.padding > .list .item,
.padding-horizontal > .list .item {
margin-right: 0;
margin-left: 0; }
.card .item.item-input input,
.list-inset .item.item-input input,
.padding > .list .item.item-input input,
.padding-horizontal > .list .item.item-input input {
padding-right: 44px; }
.padding-left > .list .item {
margin-left: 0; }
.padding-right > .list .item {
margin-right: 0; }
/**
* Badges
* --------------------------------------------------
*/
.badge {
background-color: transparent;
color: #AAAAAA;
z-index: 1;
display: inline-block;
padding: 3px 8px;
min-width: 10px;
border-radius: 10px;
vertical-align: baseline;
text-align: center;
white-space: nowrap;
font-weight: bold;
font-size: 14px;
line-height: 16px; }
.badge:empty {
display: none; }
.tabs .tab-item .badge.badge-light,
.badge.badge-light {
background-color: #fff;
color: #444; }
.tabs .tab-item .badge.badge-stable,
.badge.badge-stable {
background-color: #f8f8f8;
color: #444; }
.tabs .tab-item .badge.badge-positive,
.badge.badge-positive {
background-color: #387ef5;
color: #fff; }
.tabs .tab-item .badge.badge-calm,
.badge.badge-calm {
background-color: #11c1f3;
color: #fff; }
.tabs .tab-item .badge.badge-assertive,
.badge.badge-assertive {
background-color: #ef473a;
color: #fff; }
.tabs .tab-item .badge.badge-balanced,
.badge.badge-balanced {
background-color: #33cd5f;
color: #fff; }
.tabs .tab-item .badge.badge-energized,
.badge.badge-energized {
background-color: #ffc900;
color: #fff; }
.tabs .tab-item .badge.badge-royal,
.badge.badge-royal {
background-color: #886aea;
color: #fff; }
.tabs .tab-item .badge.badge-dark,
.badge.badge-dark {
background-color: #444;
color: #fff; }
.button .badge {
position: relative;
top: -1px; }
/**
* Slide Box
* --------------------------------------------------
*/
.slider {
position: relative;
visibility: hidden;
overflow: hidden; }
.slider-slides {
position: relative;
height: 100%; }
.slider-slide {
position: relative;
display: block;
float: left;
width: 100%;
height: 100%;
vertical-align: top; }
.slider-slide-image > img {
width: 100%; }
.slider-pager {
position: absolute;
bottom: 20px;
z-index: 1;
width: 100%;
height: 15px;
text-align: center; }
.slider-pager .slider-pager-page {
display: inline-block;
margin: 0px 3px;
width: 15px;
color: #000;
text-decoration: none;
opacity: 0.3; }
.slider-pager .slider-pager-page.active {
-webkit-transition: opacity 0.4s ease-in;
transition: opacity 0.4s ease-in;
opacity: 1; }
.slider-slide.ng-enter, .slider-slide.ng-leave, .slider-slide.ng-animate,
.slider-pager-page.ng-enter,
.slider-pager-page.ng-leave,
.slider-pager-page.ng-animate {
-webkit-transition: none !important;
transition: none !important; }
.slider-slide.ng-animate,
.slider-pager-page.ng-animate {
-webkit-animation: none 0s;
animation: none 0s; }
/**
* Swiper 3.2.7
* Most modern mobile touch slider and framework with hardware accelerated transitions
*
* http://www.idangero.us/swiper/
*
* Copyright 2015, Vladimir Kharlampidi
* The iDangero.us
* http://www.idangero.us/
*
* Licensed under MIT
*
* Released on: December 7, 2015
*/
.swiper-container {
margin: 0 auto;
position: relative;
overflow: hidden;
/* Fix of Webkit flickering */
z-index: 1; }
.swiper-container-no-flexbox .swiper-slide {
float: left; }
.swiper-container-vertical > .swiper-wrapper {
-webkit-box-orient: vertical;
-moz-box-orient: vertical;
-ms-flex-direction: column;
-webkit-flex-direction: column;
flex-direction: column; }
.swiper-wrapper {
position: relative;
width: 100%;
height: 100%;
z-index: 1;
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
-webkit-transition-property: -webkit-transform;
-moz-transition-property: -moz-transform;
-o-transition-property: -o-transform;
-ms-transition-property: -ms-transform;
transition-property: transform;
-webkit-box-sizing: content-box;
-moz-box-sizing: content-box;
box-sizing: content-box; }
.swiper-container-android .swiper-slide,
.swiper-wrapper {
-webkit-transform: translate3d(0px, 0, 0);
-moz-transform: translate3d(0px, 0, 0);
-o-transform: translate(0px, 0px);
-ms-transform: translate3d(0px, 0, 0);
transform: translate3d(0px, 0, 0); }
.swiper-container-multirow > .swiper-wrapper {
-webkit-box-lines: multiple;
-moz-box-lines: multiple;
-ms-flex-wrap: wrap;
-webkit-flex-wrap: wrap;
flex-wrap: wrap; }
.swiper-container-free-mode > .swiper-wrapper {
-webkit-transition-timing-function: ease-out;
-moz-transition-timing-function: ease-out;
-ms-transition-timing-function: ease-out;
-o-transition-timing-function: ease-out;
transition-timing-function: ease-out;
margin: 0 auto; }
.swiper-slide {
display: block;
-webkit-flex-shrink: 0;
-ms-flex: 0 0 auto;
flex-shrink: 0;
width: 100%;
height: 100%;
position: relative; }
/* Auto Height */
.swiper-container-autoheight,
.swiper-container-autoheight .swiper-slide {
height: auto; }
.swiper-container-autoheight .swiper-wrapper {
-webkit-box-align: start;
-ms-flex-align: start;
-webkit-align-items: flex-start;
align-items: flex-start;
-webkit-transition-property: -webkit-transform, height;
-moz-transition-property: -moz-transform;
-o-transition-property: -o-transform;
-ms-transition-property: -ms-transform;
transition-property: transform, height; }
/* a11y */
.swiper-container .swiper-notification {
position: absolute;
left: 0;
top: 0;
pointer-events: none;
opacity: 0;
z-index: -1000; }
/* IE10 Windows Phone 8 Fixes */
.swiper-wp8-horizontal {
-ms-touch-action: pan-y;
touch-action: pan-y; }
.swiper-wp8-vertical {
-ms-touch-action: pan-x;
touch-action: pan-x; }
/* Arrows */
.swiper-button-prev,
.swiper-button-next {
position: absolute;
top: 50%;
width: 27px;
height: 44px;
margin-top: -22px;
z-index: 10;
cursor: pointer;
-moz-background-size: 27px 44px;
-webkit-background-size: 27px 44px;
background-size: 27px 44px;
background-position: center;
background-repeat: no-repeat; }
.swiper-button-prev.swiper-button-disabled,
.swiper-button-next.swiper-button-disabled {
opacity: 0.35;
cursor: auto;
pointer-events: none; }
.swiper-button-prev,
.swiper-container-rtl .swiper-button-next {
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%2027%2044'%3E%3Cpath%20d%3D'M0%2C22L22%2C0l2.1%2C2.1L4.2%2C22l19.9%2C19.9L22%2C44L0%2C22L0%2C22L0%2C22z'%20fill%3D'%23007aff'%2F%3E%3C%2Fsvg%3E");
left: 10px;
right: auto; }
.swiper-button-prev.swiper-button-black,
.swiper-container-rtl .swiper-button-next.swiper-button-black {
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%2027%2044'%3E%3Cpath%20d%3D'M0%2C22L22%2C0l2.1%2C2.1L4.2%2C22l19.9%2C19.9L22%2C44L0%2C22L0%2C22L0%2C22z'%20fill%3D'%23000000'%2F%3E%3C%2Fsvg%3E"); }
.swiper-button-prev.swiper-button-white,
.swiper-container-rtl .swiper-button-next.swiper-button-white {
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%2027%2044'%3E%3Cpath%20d%3D'M0%2C22L22%2C0l2.1%2C2.1L4.2%2C22l19.9%2C19.9L22%2C44L0%2C22L0%2C22L0%2C22z'%20fill%3D'%23ffffff'%2F%3E%3C%2Fsvg%3E"); }
.swiper-button-next,
.swiper-container-rtl .swiper-button-prev {
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%2027%2044'%3E%3Cpath%20d%3D'M27%2C22L27%2C22L5%2C44l-2.1-2.1L22.8%2C22L2.9%2C2.1L5%2C0L27%2C22L27%2C22z'%20fill%3D'%23007aff'%2F%3E%3C%2Fsvg%3E");
right: 10px;
left: auto; }
.swiper-button-next.swiper-button-black,
.swiper-container-rtl .swiper-button-prev.swiper-button-black {
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%2027%2044'%3E%3Cpath%20d%3D'M27%2C22L27%2C22L5%2C44l-2.1-2.1L22.8%2C22L2.9%2C2.1L5%2C0L27%2C22L27%2C22z'%20fill%3D'%23000000'%2F%3E%3C%2Fsvg%3E"); }
.swiper-button-next.swiper-button-white,
.swiper-container-rtl .swiper-button-prev.swiper-button-white {
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%2027%2044'%3E%3Cpath%20d%3D'M27%2C22L27%2C22L5%2C44l-2.1-2.1L22.8%2C22L2.9%2C2.1L5%2C0L27%2C22L27%2C22z'%20fill%3D'%23ffffff'%2F%3E%3C%2Fsvg%3E"); }
/* Pagination Styles */
.swiper-pagination {
position: absolute;
text-align: center;
-webkit-transition: 300ms;
-moz-transition: 300ms;
-o-transition: 300ms;
transition: 300ms;
-webkit-transform: translate3d(0, 0, 0);
-ms-transform: translate3d(0, 0, 0);
-o-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
z-index: 10; }
.swiper-pagination.swiper-pagination-hidden {
opacity: 0; }
.swiper-pagination-bullet {
width: 8px;
height: 8px;
display: inline-block;
border-radius: 100%;
background: #000;
opacity: 0.2; }
button.swiper-pagination-bullet {
border: none;
margin: 0;
padding: 0;
box-shadow: none;
-moz-appearance: none;
-ms-appearance: none;
-webkit-appearance: none;
appearance: none; }
.swiper-pagination-clickable .swiper-pagination-bullet {
cursor: pointer; }
.swiper-pagination-white .swiper-pagination-bullet {
background: #fff; }
.swiper-pagination-bullet-active {
opacity: 1; }
.swiper-pagination-white .swiper-pagination-bullet-active {
background: #fff; }
.swiper-pagination-black .swiper-pagination-bullet-active {
background: #000; }
.swiper-container-vertical > .swiper-pagination {
right: 10px;
top: 50%;
-webkit-transform: translate3d(0px, -50%, 0);
-moz-transform: translate3d(0px, -50%, 0);
-o-transform: translate(0px, -50%);
-ms-transform: translate3d(0px, -50%, 0);
transform: translate3d(0px, -50%, 0); }
.swiper-container-vertical > .swiper-pagination .swiper-pagination-bullet {
margin: 5px 0;
display: block; }
.swiper-container-horizontal > .swiper-pagination {
bottom: 10px;
left: 0;
width: 100%; }
.swiper-container-horizontal > .swiper-pagination .swiper-pagination-bullet {
margin: 0 5px; }
/* 3D Container */
.swiper-container-3d {
-webkit-perspective: 1200px;
-moz-perspective: 1200px;
-o-perspective: 1200px;
perspective: 1200px; }
.swiper-container-3d .swiper-wrapper,
.swiper-container-3d .swiper-slide,
.swiper-container-3d .swiper-slide-shadow-left,
.swiper-container-3d .swiper-slide-shadow-right,
.swiper-container-3d .swiper-slide-shadow-top,
.swiper-container-3d .swiper-slide-shadow-bottom,
.swiper-container-3d .swiper-cube-shadow {
-webkit-transform-style: preserve-3d;
-moz-transform-style: preserve-3d;
-ms-transform-style: preserve-3d;
transform-style: preserve-3d; }
.swiper-container-3d .swiper-slide-shadow-left,
.swiper-container-3d .swiper-slide-shadow-right,
.swiper-container-3d .swiper-slide-shadow-top,
.swiper-container-3d .swiper-slide-shadow-bottom {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 10; }
.swiper-container-3d .swiper-slide-shadow-left {
background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, 0.5)), to(transparent));
/* Safari 4+, Chrome */
background-image: -webkit-linear-gradient(right, rgba(0, 0, 0, 0.5), transparent);
/* Chrome 10+, Safari 5.1+, iOS 5+ */
background-image: -moz-linear-gradient(right, rgba(0, 0, 0, 0.5), transparent);
/* Firefox 3.6-15 */
background-image: -o-linear-gradient(right, rgba(0, 0, 0, 0.5), transparent);
/* Opera 11.10-12.00 */
background-image: linear-gradient(to left, rgba(0, 0, 0, 0.5), transparent);
/* Firefox 16+, IE10, Opera 12.50+ */ }
.swiper-container-3d .swiper-slide-shadow-right {
background-image: -webkit-gradient(linear, right top, left top, from(rgba(0, 0, 0, 0.5)), to(transparent));
/* Safari 4+, Chrome */
background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, 0.5), transparent);
/* Chrome 10+, Safari 5.1+, iOS 5+ */
background-image: -moz-linear-gradient(left, rgba(0, 0, 0, 0.5), transparent);
/* Firefox 3.6-15 */
background-image: -o-linear-gradient(left, rgba(0, 0, 0, 0.5), transparent);
/* Opera 11.10-12.00 */
background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5), transparent);
/* Firefox 16+, IE10, Opera 12.50+ */ }
.swiper-container-3d .swiper-slide-shadow-top {
background-image: -webkit-gradient(linear, left top, left bottom, from(rgba(0, 0, 0, 0.5)), to(transparent));
/* Safari 4+, Chrome */
background-image: -webkit-linear-gradient(bottom, rgba(0, 0, 0, 0.5), transparent);
/* Chrome 10+, Safari 5.1+, iOS 5+ */
background-image: -moz-linear-gradient(bottom, rgba(0, 0, 0, 0.5), transparent);
/* Firefox 3.6-15 */
background-image: -o-linear-gradient(bottom, rgba(0, 0, 0, 0.5), transparent);
/* Opera 11.10-12.00 */
background-image: linear-gradient(to top, rgba(0, 0, 0, 0.5), transparent);
/* Firefox 16+, IE10, Opera 12.50+ */ }
.swiper-container-3d .swiper-slide-shadow-bottom {
background-image: -webkit-gradient(linear, left bottom, left top, from(rgba(0, 0, 0, 0.5)), to(transparent));
/* Safari 4+, Chrome */
background-image: -webkit-linear-gradient(top, rgba(0, 0, 0, 0.5), transparent);
/* Chrome 10+, Safari 5.1+, iOS 5+ */
background-image: -moz-linear-gradient(top, rgba(0, 0, 0, 0.5), transparent);
/* Firefox 3.6-15 */
background-image: -o-linear-gradient(top, rgba(0, 0, 0, 0.5), transparent);
/* Opera 11.10-12.00 */
background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.5), transparent);
/* Firefox 16+, IE10, Opera 12.50+ */ }
/* Coverflow */
.swiper-container-coverflow .swiper-wrapper {
/* Windows 8 IE 10 fix */
-ms-perspective: 1200px; }
/* Fade */
.swiper-container-fade.swiper-container-free-mode .swiper-slide {
-webkit-transition-timing-function: ease-out;
-moz-transition-timing-function: ease-out;
-ms-transition-timing-function: ease-out;
-o-transition-timing-function: ease-out;
transition-timing-function: ease-out; }
.swiper-container-fade .swiper-slide {
pointer-events: none; }
.swiper-container-fade .swiper-slide .swiper-slide {
pointer-events: none; }
.swiper-container-fade .swiper-slide-active,
.swiper-container-fade .swiper-slide-active .swiper-slide-active {
pointer-events: auto; }
/* Cube */
.swiper-container-cube {
overflow: visible; }
.swiper-container-cube .swiper-slide {
pointer-events: none;
visibility: hidden;
-webkit-transform-origin: 0 0;
-moz-transform-origin: 0 0;
-ms-transform-origin: 0 0;
transform-origin: 0 0;
-webkit-backface-visibility: hidden;
-moz-backface-visibility: hidden;
-ms-backface-visibility: hidden;
backface-visibility: hidden;
width: 100%;
height: 100%;
z-index: 1; }
.swiper-container-cube.swiper-container-rtl .swiper-slide {
-webkit-transform-origin: 100% 0;
-moz-transform-origin: 100% 0;
-ms-transform-origin: 100% 0;
transform-origin: 100% 0; }
.swiper-container-cube .swiper-slide-active,
.swiper-container-cube .swiper-slide-next,
.swiper-container-cube .swiper-slide-prev,
.swiper-container-cube .swiper-slide-next + .swiper-slide {
pointer-events: auto;
visibility: visible; }
.swiper-container-cube .swiper-slide-shadow-top,
.swiper-container-cube .swiper-slide-shadow-bottom,
.swiper-container-cube .swiper-slide-shadow-left,
.swiper-container-cube .swiper-slide-shadow-right {
z-index: 0;
-webkit-backface-visibility: hidden;
-moz-backface-visibility: hidden;
-ms-backface-visibility: hidden;
backface-visibility: hidden; }
.swiper-container-cube .swiper-cube-shadow {
position: absolute;
left: 0;
bottom: 0px;
width: 100%;
height: 100%;
background: #000;
opacity: 0.6;
-webkit-filter: blur(50px);
filter: blur(50px);
z-index: 0; }
/* Scrollbar */
.swiper-scrollbar {
border-radius: 10px;
position: relative;
-ms-touch-action: none;
background: rgba(0, 0, 0, 0.1); }
.swiper-container-horizontal > .swiper-scrollbar {
position: absolute;
left: 1%;
bottom: 3px;
z-index: 50;
height: 5px;
width: 98%; }
.swiper-container-vertical > .swiper-scrollbar {
position: absolute;
right: 3px;
top: 1%;
z-index: 50;
width: 5px;
height: 98%; }
.swiper-scrollbar-drag {
height: 100%;
width: 100%;
position: relative;
background: rgba(0, 0, 0, 0.5);
border-radius: 10px;
left: 0;
top: 0; }
.swiper-scrollbar-cursor-drag {
cursor: move; }
/* Preloader */
.swiper-lazy-preloader {
width: 42px;
height: 42px;
position: absolute;
left: 50%;
top: 50%;
margin-left: -21px;
margin-top: -21px;
z-index: 10;
-webkit-transform-origin: 50%;
-moz-transform-origin: 50%;
transform-origin: 50%;
-webkit-animation: swiper-preloader-spin 1s steps(12, end) infinite;
-moz-animation: swiper-preloader-spin 1s steps(12, end) infinite;
animation: swiper-preloader-spin 1s steps(12, end) infinite; }
.swiper-lazy-preloader:after {
display: block;
content: "";
width: 100%;
height: 100%;
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20viewBox%3D'0%200%20120%20120'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20xmlns%3Axlink%3D'http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink'%3E%3Cdefs%3E%3Cline%20id%3D'l'%20x1%3D'60'%20x2%3D'60'%20y1%3D'7'%20y2%3D'27'%20stroke%3D'%236c6c6c'%20stroke-width%3D'11'%20stroke-linecap%3D'round'%2F%3E%3C%2Fdefs%3E%3Cg%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(30%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(60%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(90%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(120%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(150%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.37'%20transform%3D'rotate(180%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.46'%20transform%3D'rotate(210%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.56'%20transform%3D'rotate(240%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.66'%20transform%3D'rotate(270%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.75'%20transform%3D'rotate(300%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.85'%20transform%3D'rotate(330%2060%2C60)'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
background-position: 50%;
-webkit-background-size: 100%;
background-size: 100%;
background-repeat: no-repeat; }
.swiper-lazy-preloader-white:after {
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20viewBox%3D'0%200%20120%20120'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20xmlns%3Axlink%3D'http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink'%3E%3Cdefs%3E%3Cline%20id%3D'l'%20x1%3D'60'%20x2%3D'60'%20y1%3D'7'%20y2%3D'27'%20stroke%3D'%23fff'%20stroke-width%3D'11'%20stroke-linecap%3D'round'%2F%3E%3C%2Fdefs%3E%3Cg%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(30%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(60%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(90%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(120%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(150%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.37'%20transform%3D'rotate(180%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.46'%20transform%3D'rotate(210%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.56'%20transform%3D'rotate(240%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.66'%20transform%3D'rotate(270%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.75'%20transform%3D'rotate(300%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.85'%20transform%3D'rotate(330%2060%2C60)'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E"); }
@-webkit-keyframes swiper-preloader-spin {
100% {
-webkit-transform: rotate(360deg); } }
@keyframes swiper-preloader-spin {
100% {
transform: rotate(360deg); } }
ion-slides {
width: 100%;
height: 100%;
display: block; }
.slide-zoom {
display: block;
width: 100%;
text-align: center; }
.swiper-container {
width: 100%;
height: 100%;
padding: 0;
overflow: hidden; }
.swiper-wrapper {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
padding: 0; }
.swiper-slide {
width: 100%;
height: 100%;
box-sizing: border-box;
/* Center slide text vertically */ }
.swiper-slide img {
width: auto;
height: auto;
max-width: 100%;
max-height: 100%; }
.scroll-refresher {
position: absolute;
top: -60px;
right: 0;
left: 0;
overflow: hidden;
margin: auto;
height: 60px; }
.scroll-refresher .ionic-refresher-content {
position: absolute;
bottom: 15px;
left: 0;
width: 100%;
color: #666666;
text-align: center;
font-size: 30px; }
.scroll-refresher .ionic-refresher-content .text-refreshing,
.scroll-refresher .ionic-refresher-content .text-pulling {
font-size: 16px;
line-height: 16px; }
.scroll-refresher .ionic-refresher-content.ionic-refresher-with-text {
bottom: 10px; }
.scroll-refresher .icon-refreshing,
.scroll-refresher .icon-pulling {
width: 100%;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
-webkit-transform-style: preserve-3d;
transform-style: preserve-3d; }
.scroll-refresher .icon-pulling {
-webkit-animation-name: refresh-spin-back;
animation-name: refresh-spin-back;
-webkit-animation-duration: 200ms;
animation-duration: 200ms;
-webkit-animation-timing-function: linear;
animation-timing-function: linear;
-webkit-animation-fill-mode: none;
animation-fill-mode: none;
-webkit-transform: translate3d(0, 0, 0) rotate(0deg);
transform: translate3d(0, 0, 0) rotate(0deg); }
.scroll-refresher .icon-refreshing,
.scroll-refresher .text-refreshing {
display: none; }
.scroll-refresher .icon-refreshing {
-webkit-animation-duration: 1.5s;
animation-duration: 1.5s; }
.scroll-refresher.active .icon-pulling:not(.pulling-rotation-disabled) {
-webkit-animation-name: refresh-spin;
animation-name: refresh-spin;
-webkit-transform: translate3d(0, 0, 0) rotate(-180deg);
transform: translate3d(0, 0, 0) rotate(-180deg); }
.scroll-refresher.active.refreshing {
-webkit-transition: -webkit-transform 0.2s;
transition: -webkit-transform 0.2s;
-webkit-transition: transform 0.2s;
transition: transform 0.2s;
-webkit-transform: scale(1, 1);
transform: scale(1, 1); }
.scroll-refresher.active.refreshing .icon-pulling,
.scroll-refresher.active.refreshing .text-pulling {
display: none; }
.scroll-refresher.active.refreshing .icon-refreshing,
.scroll-refresher.active.refreshing .text-refreshing {
display: block; }
.scroll-refresher.active.refreshing.refreshing-tail {
-webkit-transform: scale(0, 0);
transform: scale(0, 0); }
.overflow-scroll > .scroll {
-webkit-overflow-scrolling: touch;
width: 100%; }
.overflow-scroll > .scroll.overscroll {
position: fixed;
right: 0;
left: 0; }
.overflow-scroll.padding > .scroll.overscroll {
padding: 10px; }
@-webkit-keyframes refresh-spin {
0% {
-webkit-transform: translate3d(0, 0, 0) rotate(0); }
100% {
-webkit-transform: translate3d(0, 0, 0) rotate(180deg); } }
@keyframes refresh-spin {
0% {
transform: translate3d(0, 0, 0) rotate(0); }
100% {
transform: translate3d(0, 0, 0) rotate(180deg); } }
@-webkit-keyframes refresh-spin-back {
0% {
-webkit-transform: translate3d(0, 0, 0) rotate(180deg); }
100% {
-webkit-transform: translate3d(0, 0, 0) rotate(0); } }
@keyframes refresh-spin-back {
0% {
transform: translate3d(0, 0, 0) rotate(180deg); }
100% {
transform: translate3d(0, 0, 0) rotate(0); } }
/**
* Spinners
* --------------------------------------------------
*/
.spinner {
stroke: #444;
fill: #444; }
.spinner svg {
width: 28px;
height: 28px; }
.spinner.spinner-light {
stroke: #fff;
fill: #fff; }
.spinner.spinner-stable {
stroke: #f8f8f8;
fill: #f8f8f8; }
.spinner.spinner-positive {
stroke: #387ef5;
fill: #387ef5; }
.spinner.spinner-calm {
stroke: #11c1f3;
fill: #11c1f3; }
.spinner.spinner-balanced {
stroke: #33cd5f;
fill: #33cd5f; }
.spinner.spinner-assertive {
stroke: #ef473a;
fill: #ef473a; }
.spinner.spinner-energized {
stroke: #ffc900;
fill: #ffc900; }
.spinner.spinner-royal {
stroke: #886aea;
fill: #886aea; }
.spinner.spinner-dark {
stroke: #444;
fill: #444; }
.spinner-android {
stroke: #4b8bf4; }
.spinner-ios,
.spinner-ios-small {
stroke: #69717d; }
.spinner-spiral .stop1 {
stop-color: #fff;
stop-opacity: 0; }
.spinner-spiral.spinner-light .stop1 {
stop-color: #444; }
.spinner-spiral.spinner-light .stop2 {
stop-color: #fff; }
.spinner-spiral.spinner-stable .stop2 {
stop-color: #f8f8f8; }
.spinner-spiral.spinner-positive .stop2 {
stop-color: #387ef5; }
.spinner-spiral.spinner-calm .stop2 {
stop-color: #11c1f3; }
.spinner-spiral.spinner-balanced .stop2 {
stop-color: #33cd5f; }
.spinner-spiral.spinner-assertive .stop2 {
stop-color: #ef473a; }
.spinner-spiral.spinner-energized .stop2 {
stop-color: #ffc900; }
.spinner-spiral.spinner-royal .stop2 {
stop-color: #886aea; }
.spinner-spiral.spinner-dark .stop2 {
stop-color: #444; }
/**
* Forms
* --------------------------------------------------
*/
form {
margin: 0 0 1.42857; }
legend {
display: block;
margin-bottom: 1.42857;
padding: 0;
width: 100%;
border: 1px solid #ddd;
color: #444;
font-size: 21px;
line-height: 2.85714; }
legend small {
color: #f8f8f8;
font-size: 1.07143; }
label,
input,
button,
select,
textarea {
font-weight: normal;
font-size: 14px;
line-height: 1.42857; }
input,
button,
select,
textarea {
font-family: "-apple-system", "Helvetica Neue", "Roboto", "Segoe UI", sans-serif; }
.item-input {
display: -webkit-box;
display: -webkit-flex;
display: -moz-box;
display: -moz-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
-webkit-align-items: center;
-moz-align-items: center;
align-items: center;
position: relative;
overflow: hidden;
padding: 6px 0 5px 16px; }
.item-input input {
-webkit-border-radius: 0;
border-radius: 0;
-webkit-box-flex: 1;
-webkit-flex: 1 220px;
-moz-box-flex: 1;
-moz-flex: 1 220px;
-ms-flex: 1 220px;
flex: 1 220px;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
margin: 0;
padding-right: 24px;
background-color: transparent; }
.item-input .button .icon {
-webkit-box-flex: 0;
-webkit-flex: 0 0 24px;
-moz-box-flex: 0;
-moz-flex: 0 0 24px;
-ms-flex: 0 0 24px;
flex: 0 0 24px;
position: static;
display: inline-block;
height: auto;
text-align: center;
font-size: 16px; }
.item-input .button-bar {
-webkit-border-radius: 0;
border-radius: 0;
-webkit-box-flex: 1;
-webkit-flex: 1 0 220px;
-moz-box-flex: 1;
-moz-flex: 1 0 220px;
-ms-flex: 1 0 220px;
flex: 1 0 220px;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none; }
.item-input .icon {
min-width: 14px; }
.platform-windowsphone .item-input input {
flex-shrink: 1; }
.item-input-inset {
display: -webkit-box;
display: -webkit-flex;
display: -moz-box;
display: -moz-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
-webkit-align-items: center;
-moz-align-items: center;
align-items: center;
position: relative;
overflow: hidden;
padding: 10.66667px; }
.item-input-wrapper {
display: -webkit-box;
display: -webkit-flex;
display: -moz-box;
display: -moz-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-flex: 1;
-webkit-flex: 1 0;
-moz-box-flex: 1;
-moz-flex: 1 0;
-ms-flex: 1 0;
flex: 1 0;
-webkit-box-align: center;
-ms-flex-align: center;
-webkit-align-items: center;
-moz-align-items: center;
align-items: center;
-webkit-border-radius: 4px;
border-radius: 4px;
padding-right: 8px;
padding-left: 8px;
background: #eee; }
.item-input-inset .item-input-wrapper input {
padding-left: 4px;
height: 29px;
background: transparent;
line-height: 18px; }
.item-input-wrapper ~ .button {
margin-left: 10.66667px; }
.input-label {
display: table;
padding: 7px 10px 7px 0px;
max-width: 200px;
width: 35%;
color: #444;
font-size: 16px; }
.placeholder-icon {
color: #aaa; }
.placeholder-icon:first-child {
padding-right: 6px; }
.placeholder-icon:last-child {
padding-left: 6px; }
.item-stacked-label {
display: block;
background-color: transparent;
box-shadow: none; }
.item-stacked-label .input-label, .item-stacked-label .icon {
display: inline-block;
padding: 4px 0 0 0px;
vertical-align: middle; }
.item-stacked-label input,
.item-stacked-label textarea {
-webkit-border-radius: 2px;
border-radius: 2px;
padding: 4px 8px 3px 0;
border: none;
background-color: #fff; }
.item-stacked-label input {
overflow: hidden;
height: 46px; }
.item-select.item-stacked-label select {
position: relative;
padding: 0px;
max-width: 90%;
direction: ltr;
white-space: pre-wrap;
margin: -3px; }
.item-floating-label {
display: block;
background-color: transparent;
box-shadow: none; }
.item-floating-label .input-label {
position: relative;
padding: 5px 0 0 0;
opacity: 0;
top: 10px;
-webkit-transition: opacity 0.15s ease-in, top 0.2s linear;
transition: opacity 0.15s ease-in, top 0.2s linear; }
.item-floating-label .input-label.has-input {
opacity: 1;
top: 0;
-webkit-transition: opacity 0.15s ease-in, top 0.2s linear;
transition: opacity 0.15s ease-in, top 0.2s linear; }
textarea,
input[type="text"],
input[type="password"],
input[type="datetime"],
input[type="datetime-local"],
input[type="date"],
input[type="month"],
input[type="time"],
input[type="week"],
input[type="number"],
input[type="email"],
input[type="url"],
input[type="search"],
input[type="tel"],
input[type="color"] {
display: block;
padding-top: 2px;
padding-left: 0;
height: 34px;
color: #111;
vertical-align: middle;
font-size: 14px;
line-height: 16px; }
.platform-ios input[type="datetime-local"],
.platform-ios input[type="date"],
.platform-ios input[type="month"],
.platform-ios input[type="time"],
.platform-ios input[type="week"],
.platform-android input[type="datetime-local"],
.platform-android input[type="date"],
.platform-android input[type="month"],
.platform-android input[type="time"],
.platform-android input[type="week"] {
padding-top: 8px; }
.item-input input,
.item-input textarea {
width: 100%; }
textarea {
padding-left: 0; }
textarea::-moz-placeholder {
color: #aaaaaa; }
textarea:-ms-input-placeholder {
color: #aaaaaa; }
textarea::-webkit-input-placeholder {
color: #aaaaaa;
text-indent: -3px; }
textarea {
height: auto; }
textarea,
input[type="text"],
input[type="password"],
input[type="datetime"],
input[type="datetime-local"],
input[type="date"],
input[type="month"],
input[type="time"],
input[type="week"],
input[type="number"],
input[type="email"],
input[type="url"],
input[type="search"],
input[type="tel"],
input[type="color"] {
border: 0; }
input[type="radio"],
input[type="checkbox"] {
margin: 0;
line-height: normal; }
.item-input input[type="file"],
.item-input input[type="image"],
.item-input input[type="submit"],
.item-input input[type="reset"],
.item-input input[type="button"],
.item-input input[type="radio"],
.item-input input[type="checkbox"] {
width: auto; }
input[type="file"] {
line-height: 34px; }
.previous-input-focus,
.cloned-text-input + input,
.cloned-text-input + textarea {
position: absolute !important;
left: -9999px;
width: 200px; }
input::-moz-placeholder,
textarea::-moz-placeholder {
color: #aaaaaa; }
input:-ms-input-placeholder,
textarea:-ms-input-placeholder {
color: #aaaaaa; }
input::-webkit-input-placeholder,
textarea::-webkit-input-placeholder {
color: #aaaaaa;
text-indent: 0; }
input[disabled],
select[disabled],
textarea[disabled],
input[readonly]:not(.cloned-text-input),
textarea[readonly]:not(.cloned-text-input),
select[readonly] {
background-color: #f8f8f8;
cursor: not-allowed; }
input[type="radio"][disabled],
input[type="checkbox"][disabled],
input[type="radio"][readonly],
input[type="checkbox"][readonly] {
background-color: transparent; }
/**
* Checkbox
* --------------------------------------------------
*/
.checkbox {
position: relative;
display: inline-block;
padding: 7px 7px;
cursor: pointer; }
.checkbox input:before,
.checkbox .checkbox-icon:before {
border-color: #ddd; }
.checkbox input:checked:before,
.checkbox input:checked + .checkbox-icon:before {
background: #387ef5;
border-color: #387ef5; }
.checkbox-light input:before,
.checkbox-light .checkbox-icon:before {
border-color: #ddd; }
.checkbox-light input:checked:before,
.checkbox-light input:checked + .checkbox-icon:before {
background: #ddd;
border-color: #ddd; }
.checkbox-stable input:before,
.checkbox-stable .checkbox-icon:before {
border-color: #b2b2b2; }
.checkbox-stable input:checked:before,
.checkbox-stable input:checked + .checkbox-icon:before {
background: #b2b2b2;
border-color: #b2b2b2; }
.checkbox-positive input:before,
.checkbox-positive .checkbox-icon:before {
border-color: #387ef5; }
.checkbox-positive input:checked:before,
.checkbox-positive input:checked + .checkbox-icon:before {
background: #387ef5;
border-color: #387ef5; }
.checkbox-calm input:before,
.checkbox-calm .checkbox-icon:before {
border-color: #11c1f3; }
.checkbox-calm input:checked:before,
.checkbox-calm input:checked + .checkbox-icon:before {
background: #11c1f3;
border-color: #11c1f3; }
.checkbox-assertive input:before,
.checkbox-assertive .checkbox-icon:before {
border-color: #ef473a; }
.checkbox-assertive input:checked:before,
.checkbox-assertive input:checked + .checkbox-icon:before {
background: #ef473a;
border-color: #ef473a; }
.checkbox-balanced input:before,
.checkbox-balanced .checkbox-icon:before {
border-color: #33cd5f; }
.checkbox-balanced input:checked:before,
.checkbox-balanced input:checked + .checkbox-icon:before {
background: #33cd5f;
border-color: #33cd5f; }
.checkbox-energized input:before,
.checkbox-energized .checkbox-icon:before {
border-color: #ffc900; }
.checkbox-energized input:checked:before,
.checkbox-energized input:checked + .checkbox-icon:before {
background: #ffc900;
border-color: #ffc900; }
.checkbox-royal input:before,
.checkbox-royal .checkbox-icon:before {
border-color: #886aea; }
.checkbox-royal input:checked:before,
.checkbox-royal input:checked + .checkbox-icon:before {
background: #886aea;
border-color: #886aea; }
.checkbox-dark input:before,
.checkbox-dark .checkbox-icon:before {
border-color: #444; }
.checkbox-dark input:checked:before,
.checkbox-dark input:checked + .checkbox-icon:before {
background: #444;
border-color: #444; }
.checkbox input:disabled:before,
.checkbox input:disabled + .checkbox-icon:before {
border-color: #ddd; }
.checkbox input:disabled:checked:before,
.checkbox input:disabled:checked + .checkbox-icon:before {
background: #ddd; }
.checkbox.checkbox-input-hidden input {
display: none !important; }
.checkbox input,
.checkbox-icon {
position: relative;
width: 28px;
height: 28px;
display: block;
border: 0;
background: transparent;
cursor: pointer;
-webkit-appearance: none; }
.checkbox input:before,
.checkbox-icon:before {
display: table;
width: 100%;
height: 100%;
border-width: 1px;
border-style: solid;
border-radius: 28px;
background: #fff;
content: ' ';
-webkit-transition: background-color 20ms ease-in-out;
transition: background-color 20ms ease-in-out; }
.checkbox input:checked:before,
input:checked + .checkbox-icon:before {
border-width: 2px; }
.checkbox input:after,
.checkbox-icon:after {
-webkit-transition: opacity 0.05s ease-in-out;
transition: opacity 0.05s ease-in-out;
-webkit-transform: rotate(-45deg);
transform: rotate(-45deg);
position: absolute;
top: 33%;
left: 25%;
display: table;
width: 14px;
height: 6px;
border: 1px solid #fff;
border-top: 0;
border-right: 0;
content: ' ';
opacity: 0; }
.platform-android .checkbox-platform input:before,
.platform-android .checkbox-platform .checkbox-icon:before,
.checkbox-square input:before,
.checkbox-square .checkbox-icon:before {
border-radius: 2px;
width: 72%;
height: 72%;
margin-top: 14%;
margin-left: 14%;
border-width: 2px; }
.platform-android .checkbox-platform input:after,
.platform-android .checkbox-platform .checkbox-icon:after,
.checkbox-square input:after,
.checkbox-square .checkbox-icon:after {
border-width: 2px;
top: 19%;
left: 25%;
width: 13px;
height: 7px; }
.platform-android .item-checkbox-right .checkbox-square .checkbox-icon::after {
top: 31%; }
.grade-c .checkbox input:after,
.grade-c .checkbox-icon:after {
-webkit-transform: rotate(0);
transform: rotate(0);
top: 3px;
left: 4px;
border: none;
color: #fff;
content: '\2713';
font-weight: bold;
font-size: 20px; }
.checkbox input:checked:after,
input:checked + .checkbox-icon:after {
opacity: 1; }
.item-checkbox {
padding-left: 60px; }
.item-checkbox.active {
box-shadow: none; }
.item-checkbox .checkbox {
position: absolute;
top: 50%;
right: 8px;
left: 8px;
z-index: 3;
margin-top: -21px; }
.item-checkbox.item-checkbox-right {
padding-right: 60px;
padding-left: 16px; }
.item-checkbox-right .checkbox input,
.item-checkbox-right .checkbox-icon {
float: right; }
/**
* Toggle
* --------------------------------------------------
*/
.item-toggle {
pointer-events: none; }
.toggle {
position: relative;
display: inline-block;
pointer-events: auto;
margin: -5px;
padding: 5px; }
.toggle input:checked + .track {
border-color: #4cd964;
background-color: #4cd964; }
.toggle.dragging .handle {
background-color: #f2f2f2 !important; }
.toggle.toggle-light input:checked + .track {
border-color: #ddd;
background-color: #ddd; }
.toggle.toggle-stable input:checked + .track {
border-color: #b2b2b2;
background-color: #b2b2b2; }
.toggle.toggle-positive input:checked + .track {
border-color: #387ef5;
background-color: #387ef5; }
.toggle.toggle-calm input:checked + .track {
border-color: #11c1f3;
background-color: #11c1f3; }
.toggle.toggle-assertive input:checked + .track {
border-color: #ef473a;
background-color: #ef473a; }
.toggle.toggle-balanced input:checked + .track {
border-color: #33cd5f;
background-color: #33cd5f; }
.toggle.toggle-energized input:checked + .track {
border-color: #ffc900;
background-color: #ffc900; }
.toggle.toggle-royal input:checked + .track {
border-color: #886aea;
background-color: #886aea; }
.toggle.toggle-dark input:checked + .track {
border-color: #444;
background-color: #444; }
.toggle input {
display: none; }
/* the track appearance when the toggle is "off" */
.toggle .track {
-webkit-transition-timing-function: ease-in-out;
transition-timing-function: ease-in-out;
-webkit-transition-duration: 0.3s;
transition-duration: 0.3s;
-webkit-transition-property: background-color, border;
transition-property: background-color, border;
display: inline-block;
box-sizing: border-box;
width: 51px;
height: 31px;
border: solid 2px #e6e6e6;
border-radius: 20px;
background-color: #fff;
content: ' ';
cursor: pointer;
pointer-events: none; }
/* Fix to avoid background color bleeding */
/* (occured on (at least) Android 4.2, Asus MeMO Pad HD7 ME173X) */
.platform-android4_2 .toggle .track {
-webkit-background-clip: padding-box; }
/* the handle (circle) thats inside the toggle's track area */
/* also the handle's appearance when it is "off" */
.toggle .handle {
-webkit-transition: 0.3s cubic-bezier(0, 1.1, 1, 1.1);
transition: 0.3s cubic-bezier(0, 1.1, 1, 1.1);
-webkit-transition-property: background-color, transform;
transition-property: background-color, transform;
position: absolute;
display: block;
width: 27px;
height: 27px;
border-radius: 27px;
background-color: #fff;
top: 7px;
left: 7px;
box-shadow: 0 2px 7px rgba(0, 0, 0, 0.35), 0 1px 1px rgba(0, 0, 0, 0.15); }
.toggle .handle:before {
position: absolute;
top: -4px;
left: -21.5px;
padding: 18.5px 34px;
content: " "; }
.toggle input:checked + .track .handle {
-webkit-transform: translate3d(20px, 0, 0);
transform: translate3d(20px, 0, 0);
background-color: #fff; }
.item-toggle.active {
box-shadow: none; }
.item-toggle,
.item-toggle.item-complex .item-content {
padding-right: 99px; }
.item-toggle.item-complex {
padding-right: 0; }
.item-toggle .toggle {
position: absolute;
top: 10px;
right: 16px;
z-index: 3; }
.toggle input:disabled + .track {
opacity: .6; }
.toggle-small .track {
border: 0;
width: 34px;
height: 15px;
background: #9e9e9e; }
.toggle-small input:checked + .track {
background: rgba(0, 150, 137, 0.5); }
.toggle-small .handle {
top: 2px;
left: 4px;
width: 21px;
height: 21px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.25); }
.toggle-small input:checked + .track .handle {
-webkit-transform: translate3d(16px, 0, 0);
transform: translate3d(16px, 0, 0);
background: #009689; }
.toggle-small.item-toggle .toggle {
top: 19px; }
.toggle-small .toggle-light input:checked + .track {
background-color: rgba(221, 221, 221, 0.5); }
.toggle-small .toggle-light input:checked + .track .handle {
background-color: #ddd; }
.toggle-small .toggle-stable input:checked + .track {
background-color: rgba(178, 178, 178, 0.5); }
.toggle-small .toggle-stable input:checked + .track .handle {
background-color: #b2b2b2; }
.toggle-small .toggle-positive input:checked + .track {
background-color: rgba(56, 126, 245, 0.5); }
.toggle-small .toggle-positive input:checked + .track .handle {
background-color: #387ef5; }
.toggle-small .toggle-calm input:checked + .track {
background-color: rgba(17, 193, 243, 0.5); }
.toggle-small .toggle-calm input:checked + .track .handle {
background-color: #11c1f3; }
.toggle-small .toggle-assertive input:checked + .track {
background-color: rgba(239, 71, 58, 0.5); }
.toggle-small .toggle-assertive input:checked + .track .handle {
background-color: #ef473a; }
.toggle-small .toggle-balanced input:checked + .track {
background-color: rgba(51, 205, 95, 0.5); }
.toggle-small .toggle-balanced input:checked + .track .handle {
background-color: #33cd5f; }
.toggle-small .toggle-energized input:checked + .track {
background-color: rgba(255, 201, 0, 0.5); }
.toggle-small .toggle-energized input:checked + .track .handle {
background-color: #ffc900; }
.toggle-small .toggle-royal input:checked + .track {
background-color: rgba(136, 106, 234, 0.5); }
.toggle-small .toggle-royal input:checked + .track .handle {
background-color: #886aea; }
.toggle-small .toggle-dark input:checked + .track {
background-color: rgba(68, 68, 68, 0.5); }
.toggle-small .toggle-dark input:checked + .track .handle {
background-color: #444; }
/**
* Radio Button Inputs
* --------------------------------------------------
*/
.item-radio {
padding: 0; }
.item-radio:hover {
cursor: pointer; }
.item-radio .item-content {
/* give some room to the right for the checkmark icon */
padding-right: 64px; }
.item-radio .radio-icon {
/* checkmark icon will be hidden by default */
position: absolute;
top: 0;
right: 0;
z-index: 3;
visibility: hidden;
padding: 14px;
height: 100%;
font-size: 24px; }
.item-radio input {
/* hide any radio button inputs elements (the ugly circles) */
position: absolute;
left: -9999px; }
.item-radio input:checked + .radio-content .item-content {
/* style the item content when its checked */
background: #f7f7f7; }
.item-radio input:checked + .radio-content .radio-icon {
/* show the checkmark icon when its checked */
visibility: visible; }
/**
* Range
* --------------------------------------------------
*/
.range input {
display: inline-block;
overflow: hidden;
margin-top: 5px;
margin-bottom: 5px;
padding-right: 2px;
padding-left: 1px;
width: auto;
height: 43px;
outline: none;
background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ccc), color-stop(100%, #ccc));
background: linear-gradient(to right, #ccc 0%, #ccc 100%);
background-position: center;
background-size: 99% 2px;
background-repeat: no-repeat;
-webkit-appearance: none;
/*
&::-ms-track{
background: transparent;
border-color: transparent;
border-width: 11px 0 16px;
color:transparent;
margin-top:20px;
}
&::-ms-thumb {
width: $range-slider-width;
height: $range-slider-height;
border-radius: $range-slider-border-radius;
background-color: $toggle-handle-off-bg-color;
border-color:$toggle-handle-off-bg-color;
box-shadow: $range-slider-box-shadow;
margin-left:1px;
margin-right:1px;
outline:none;
}
&::-ms-fill-upper {
height: $range-track-height;
background:$range-default-track-bg;
}
*/ }
.range input::-moz-focus-outer {
/* hide the focus outline in Firefox */
border: 0; }
.range input::-webkit-slider-thumb {
position: relative;
width: 28px;
height: 28px;
border-radius: 50%;
background-color: #fff;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.3), 0 3px 5px rgba(0, 0, 0, 0.2);
cursor: pointer;
-webkit-appearance: none;
border: 0; }
.range input::-webkit-slider-thumb:before {
/* what creates the colorful line on the left side of the slider */
position: absolute;
top: 13px;
left: -2001px;
width: 2000px;
height: 2px;
background: #444;
content: ' '; }
.range input::-webkit-slider-thumb:after {
/* create a larger (but hidden) hit area */
position: absolute;
top: -15px;
left: -15px;
padding: 30px;
content: ' '; }
.range input::-ms-fill-lower {
height: 2px;
background: #444; }
.range {
display: -webkit-box;
display: -webkit-flex;
display: -moz-box;
display: -moz-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
-webkit-align-items: center;
-moz-align-items: center;
align-items: center;
padding: 2px 11px; }
.range.range-light input::-webkit-slider-thumb:before {
background: #ddd; }
.range.range-light input::-ms-fill-lower {
background: #ddd; }
.range.range-stable input::-webkit-slider-thumb:before {
background: #b2b2b2; }
.range.range-stable input::-ms-fill-lower {
background: #b2b2b2; }
.range.range-positive input::-webkit-slider-thumb:before {
background: #387ef5; }
.range.range-positive input::-ms-fill-lower {
background: #387ef5; }
.range.range-calm input::-webkit-slider-thumb:before {
background: #11c1f3; }
.range.range-calm input::-ms-fill-lower {
background: #11c1f3; }
.range.range-balanced input::-webkit-slider-thumb:before {
background: #33cd5f; }
.range.range-balanced input::-ms-fill-lower {
background: #33cd5f; }
.range.range-assertive input::-webkit-slider-thumb:before {
background: #ef473a; }
.range.range-assertive input::-ms-fill-lower {
background: #ef473a; }
.range.range-energized input::-webkit-slider-thumb:before {
background: #ffc900; }
.range.range-energized input::-ms-fill-lower {
background: #ffc900; }
.range.range-royal input::-webkit-slider-thumb:before {
background: #886aea; }
.range.range-royal input::-ms-fill-lower {
background: #886aea; }
.range.range-dark input::-webkit-slider-thumb:before {
background: #444; }
.range.range-dark input::-ms-fill-lower {
background: #444; }
.range .icon {
-webkit-box-flex: 0;
-webkit-flex: 0;
-moz-box-flex: 0;
-moz-flex: 0;
-ms-flex: 0;
flex: 0;
display: block;
min-width: 24px;
text-align: center;
font-size: 24px; }
.range input {
-webkit-box-flex: 1;
-webkit-flex: 1;
-moz-box-flex: 1;
-moz-flex: 1;
-ms-flex: 1;
flex: 1;
display: block;
margin-right: 10px;
margin-left: 10px; }
.range-label {
-webkit-box-flex: 0;
-webkit-flex: 0 0 auto;
-moz-box-flex: 0;
-moz-flex: 0 0 auto;
-ms-flex: 0 0 auto;
flex: 0 0 auto;
display: block;
white-space: nowrap; }
.range-label:first-child {
padding-left: 5px; }
.range input + .range-label {
padding-right: 5px;
padding-left: 0; }
.platform-windowsphone .range input {
height: auto; }
/**
* Select
* --------------------------------------------------
*/
.item-select {
position: relative; }
.item-select select {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
position: absolute;
top: 0;
bottom: 0;
right: 0;
padding: 0 48px 0 16px;
max-width: 65%;
border: none;
background: #fff;
color: #333;
text-indent: .01px;
text-overflow: '';
white-space: nowrap;
font-size: 14px;
cursor: pointer;
direction: rtl; }
.item-select select::-ms-expand {
display: none; }
.item-select option {
direction: ltr; }
.item-select:after {
position: absolute;
top: 50%;
right: 16px;
margin-top: -3px;
width: 0;
height: 0;
border-top: 5px solid;
border-right: 5px solid transparent;
border-left: 5px solid transparent;
color: #999;
content: "";
pointer-events: none; }
.item-select.item-light select {
background: #fff;
color: #444; }
.item-select.item-stable select {
background: #f8f8f8;
color: #444; }
.item-select.item-stable:after, .item-select.item-stable .input-label {
color: #666666; }
.item-select.item-positive select {
background: #387ef5;
color: #fff; }
.item-select.item-positive:after, .item-select.item-positive .input-label {
color: #fff; }
.item-select.item-calm select {
background: #11c1f3;
color: #fff; }
.item-select.item-calm:after, .item-select.item-calm .input-label {
color: #fff; }
.item-select.item-assertive select {
background: #ef473a;
color: #fff; }
.item-select.item-assertive:after, .item-select.item-assertive .input-label {
color: #fff; }
.item-select.item-balanced select {
background: #33cd5f;
color: #fff; }
.item-select.item-balanced:after, .item-select.item-balanced .input-label {
color: #fff; }
.item-select.item-energized select {
background: #ffc900;
color: #fff; }
.item-select.item-energized:after, .item-select.item-energized .input-label {
color: #fff; }
.item-select.item-royal select {
background: #886aea;
color: #fff; }
.item-select.item-royal:after, .item-select.item-royal .input-label {
color: #fff; }
.item-select.item-dark select {
background: #444;
color: #fff; }
.item-select.item-dark:after, .item-select.item-dark .input-label {
color: #fff; }
select[multiple], select[size] {
height: auto; }
/**
* Progress
* --------------------------------------------------
*/
progress {
display: block;
margin: 15px auto;
width: 100%; }
/**
* Buttons
* --------------------------------------------------
*/
.button {
border-color: transparent;
background-color: #f8f8f8;
color: #444;
position: relative;
display: inline-block;
margin: 0;
padding: 0 12px;
min-width: 52px;
min-height: 47px;
border-width: 1px;
border-style: solid;
border-radius: 4px;
vertical-align: top;
text-align: center;
text-overflow: ellipsis;
font-size: 16px;
line-height: 42px;
cursor: pointer; }
.button:hover {
color: #444;
text-decoration: none; }
.button.active, .button.activated {
background-color: #e5e5e5; }
.button:after {
position: absolute;
top: -6px;
right: -6px;
bottom: -6px;
left: -6px;
content: ' '; }
.button .icon {
vertical-align: top;
pointer-events: none; }
.button .icon:before, .button.icon:before, .button.icon-left:before, .button.icon-right:before {
display: inline-block;
padding: 0 0 1px 0;
vertical-align: inherit;
font-size: 24px;
line-height: 41px;
pointer-events: none; }
.button.icon-left:before {
float: left;
padding-right: .2em;
padding-left: 0; }
.button.icon-right:before {
float: right;
padding-right: 0;
padding-left: .2em; }
.button.button-block, .button.button-full {
margin-top: 10px;
margin-bottom: 10px; }
.button.button-light {
border-color: transparent;
background-color: #fff;
color: #444; }
.button.button-light:hover {
color: #444;
text-decoration: none; }
.button.button-light.active, .button.button-light.activated {
background-color: #fafafa; }
.button.button-light.button-clear {
border-color: transparent;
background: none;
box-shadow: none;
color: #ddd; }
.button.button-light.button-icon {
border-color: transparent;
background: none; }
.button.button-light.button-outline {
border-color: #ddd;
background: transparent;
color: #ddd; }
.button.button-light.button-outline.active, .button.button-light.button-outline.activated {
background-color: #ddd;
box-shadow: none;
color: #fff; }
.button.button-stable {
border-color: transparent;
background-color: #f8f8f8;
color: #444; }
.button.button-stable:hover {
color: #444;
text-decoration: none; }
.button.button-stable.active, .button.button-stable.activated {
background-color: #e5e5e5; }
.button.button-stable.button-clear {
border-color: transparent;
background: none;
box-shadow: none;
color: #b2b2b2; }
.button.button-stable.button-icon {
border-color: transparent;
background: none; }
.button.button-stable.button-outline {
border-color: #b2b2b2;
background: transparent;
color: #b2b2b2; }
.button.button-stable.button-outline.active, .button.button-stable.button-outline.activated {
background-color: #b2b2b2;
box-shadow: none;
color: #fff; }
.button.button-positive {
border-color: transparent;
background-color: #387ef5;
color: #fff; }
.button.button-positive:hover {
color: #fff;
text-decoration: none; }
.button.button-positive.active, .button.button-positive.activated {
background-color: #0c60ee; }
.button.button-positive.button-clear {
border-color: transparent;
background: none;
box-shadow: none;
color: #387ef5; }
.button.button-positive.button-icon {
border-color: transparent;
background: none; }
.button.button-positive.button-outline {
border-color: #387ef5;
background: transparent;
color: #387ef5; }
.button.button-positive.button-outline.active, .button.button-positive.button-outline.activated {
background-color: #387ef5;
box-shadow: none;
color: #fff; }
.button.button-calm {
border-color: transparent;
background-color: #11c1f3;
color: #fff; }
.button.button-calm:hover {
color: #fff;
text-decoration: none; }
.button.button-calm.active, .button.button-calm.activated {
background-color: #0a9dc7; }
.button.button-calm.button-clear {
border-color: transparent;
background: none;
box-shadow: none;
color: #11c1f3; }
.button.button-calm.button-icon {
border-color: transparent;
background: none; }
.button.button-calm.button-outline {
border-color: #11c1f3;
background: transparent;
color: #11c1f3; }
.button.button-calm.button-outline.active, .button.button-calm.button-outline.activated {
background-color: #11c1f3;
box-shadow: none;
color: #fff; }
.button.button-assertive {
border-color: transparent;
background-color: #ef473a;
color: #fff; }
.button.button-assertive:hover {
color: #fff;
text-decoration: none; }
.button.button-assertive.active, .button.button-assertive.activated {
background-color: #e42112; }
.button.button-assertive.button-clear {
border-color: transparent;
background: none;
box-shadow: none;
color: #ef473a; }
.button.button-assertive.button-icon {
border-color: transparent;
background: none; }
.button.button-assertive.button-outline {
border-color: #ef473a;
background: transparent;
color: #ef473a; }
.button.button-assertive.button-outline.active, .button.button-assertive.button-outline.activated {
background-color: #ef473a;
box-shadow: none;
color: #fff; }
.button.button-balanced {
border-color: transparent;
background-color: #33cd5f;
color: #fff; }
.button.button-balanced:hover {
color: #fff;
text-decoration: none; }
.button.button-balanced.active, .button.button-balanced.activated {
background-color: #28a54c; }
.button.button-balanced.button-clear {
border-color: transparent;
background: none;
box-shadow: none;
color: #33cd5f; }
.button.button-balanced.button-icon {
border-color: transparent;
background: none; }
.button.button-balanced.button-outline {
border-color: #33cd5f;
background: transparent;
color: #33cd5f; }
.button.button-balanced.button-outline.active, .button.button-balanced.button-outline.activated {
background-color: #33cd5f;
box-shadow: none;
color: #fff; }
.button.button-energized {
border-color: transparent;
background-color: #ffc900;
color: #fff; }
.button.button-energized:hover {
color: #fff;
text-decoration: none; }
.button.button-energized.active, .button.button-energized.activated {
background-color: #e6b500; }
.button.button-energized.button-clear {
border-color: transparent;
background: none;
box-shadow: none;
color: #ffc900; }
.button.button-energized.button-icon {
border-color: transparent;
background: none; }
.button.button-energized.button-outline {
border-color: #ffc900;
background: transparent;
color: #ffc900; }
.button.button-energized.button-outline.active, .button.button-energized.button-outline.activated {
background-color: #ffc900;
box-shadow: none;
color: #fff; }
.button.button-royal {
border-color: transparent;
background-color: #886aea;
color: #fff; }
.button.button-royal:hover {
color: #fff;
text-decoration: none; }
.button.button-royal.active, .button.button-royal.activated {
background-color: #6b46e5; }
.button.button-royal.button-clear {
border-color: transparent;
background: none;
box-shadow: none;
color: #886aea; }
.button.button-royal.button-icon {
border-color: transparent;
background: none; }
.button.button-royal.button-outline {
border-color: #886aea;
background: transparent;
color: #886aea; }
.button.button-royal.button-outline.active, .button.button-royal.button-outline.activated {
background-color: #886aea;
box-shadow: none;
color: #fff; }
.button.button-dark {
border-color: transparent;
background-color: #444;
color: #fff; }
.button.button-dark:hover {
color: #fff;
text-decoration: none; }
.button.button-dark.active, .button.button-dark.activated {
background-color: #262626; }
.button.button-dark.button-clear {
border-color: transparent;
background: none;
box-shadow: none;
color: #444; }
.button.button-dark.button-icon {
border-color: transparent;
background: none; }
.button.button-dark.button-outline {
border-color: #444;
background: transparent;
color: #444; }
.button.button-dark.button-outline.active, .button.button-dark.button-outline.activated {
background-color: #444;
box-shadow: none;
color: #fff; }
.button-small {
padding: 2px 4px 1px;
min-width: 28px;
min-height: 30px;
font-size: 12px;
line-height: 26px; }
.button-small .icon:before, .button-small.icon:before, .button-small.icon-left:before, .button-small.icon-right:before {
font-size: 16px;
line-height: 19px;
margin-top: 3px; }
.button-large {
padding: 0 16px;
min-width: 68px;
min-height: 59px;
font-size: 20px;
line-height: 53px; }
.button-large .icon:before, .button-large.icon:before, .button-large.icon-left:before, .button-large.icon-right:before {
padding-bottom: 2px;
font-size: 32px;
line-height: 51px; }
.button-icon {
-webkit-transition: opacity 0.1s;
transition: opacity 0.1s;
padding: 0 6px;
min-width: initial;
border-color: transparent;
background: none; }
.button-icon.button.active, .button-icon.button.activated {
border-color: transparent;
background: none;
box-shadow: none;
opacity: 0.3; }
.button-icon .icon:before, .button-icon.icon:before {
font-size: 32px; }
.button-clear {
-webkit-transition: opacity 0.1s;
transition: opacity 0.1s;
padding: 0 6px;
max-height: 42px;
border-color: transparent;
background: none;
box-shadow: none; }
.button-clear.button-clear {
border-color: transparent;
background: none;
box-shadow: none;
color: #b2b2b2; }
.button-clear.button-icon {
border-color: transparent;
background: none; }
.button-clear.active, .button-clear.activated {
opacity: 0.3; }
.button-outline {
-webkit-transition: opacity 0.1s;
transition: opacity 0.1s;
background: none;
box-shadow: none; }
.button-outline.button-outline {
border-color: #b2b2b2;
background: transparent;
color: #b2b2b2; }
.button-outline.button-outline.active, .button-outline.button-outline.activated {
background-color: #b2b2b2;
box-shadow: none;
color: #fff; }
.padding > .button.button-block:first-child {
margin-top: 0; }
.button-block {
display: block;
clear: both; }
.button-block:after {
clear: both; }
.button-full,
.button-full > .button {
display: block;
margin-right: 0;
margin-left: 0;
border-right-width: 0;
border-left-width: 0;
border-radius: 0; }
button.button-block,
button.button-full,
.button-full > button.button,
input.button.button-block {
width: 100%; }
a.button {
text-decoration: none; }
a.button .icon:before, a.button.icon:before, a.button.icon-left:before, a.button.icon-right:before {
margin-top: 2px; }
.button.disabled,
.button[disabled] {
opacity: .4;
cursor: default !important;
pointer-events: none; }
/**
* Button Bar
* --------------------------------------------------
*/
.button-bar {
display: -webkit-box;
display: -webkit-flex;
display: -moz-box;
display: -moz-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-flex: 1;
-webkit-flex: 1;
-moz-box-flex: 1;
-moz-flex: 1;
-ms-flex: 1;
flex: 1;
width: 100%; }
.button-bar.button-bar-inline {
display: block;
width: auto;
*zoom: 1; }
.button-bar.button-bar-inline:before, .button-bar.button-bar-inline:after {
display: table;
content: "";
line-height: 0; }
.button-bar.button-bar-inline:after {
clear: both; }
.button-bar.button-bar-inline > .button {
width: auto;
display: inline-block;
float: left; }
.button-bar > .button {
-webkit-box-flex: 1;
-webkit-flex: 1;
-moz-box-flex: 1;
-moz-flex: 1;
-ms-flex: 1;
flex: 1;
display: block;
overflow: hidden;
padding: 0 16px;
width: 0;
border-width: 1px 0px 1px 1px;
border-radius: 0;
text-align: center;
text-overflow: ellipsis;
white-space: nowrap; }
.button-bar > .button:before,
.button-bar > .button .icon:before {
line-height: 44px; }
.button-bar > .button:first-child {
border-radius: 4px 0px 0px 4px; }
.button-bar > .button:last-child {
border-right-width: 1px;
border-radius: 0px 4px 4px 0px; }
.button-bar > .button:only-child {
border-radius: 4px; }
.button-bar > .button-small:before,
.button-bar > .button-small .icon:before {
line-height: 28px; }
/**
* Grid
* --------------------------------------------------
* Using flexbox for the grid, inspired by Philip Walton:
* http://philipwalton.github.io/solved-by-flexbox/demos/grids/
* By default each .col within a .row will evenly take up
* available width, and the height of each .col with take
* up the height of the tallest .col in the same .row.
*/
.row {
display: -webkit-box;
display: -webkit-flex;
display: -moz-box;
display: -moz-flex;
display: -ms-flexbox;
display: flex;
padding: 5px;
width: 100%; }
.row-wrap {
-webkit-flex-wrap: wrap;
-moz-flex-wrap: wrap;
-ms-flex-wrap: wrap;
flex-wrap: wrap; }
.row-no-padding {
padding: 0; }
.row-no-padding > .col {
padding: 0; }
.row + .row {
margin-top: -5px;
padding-top: 0; }
.col {
-webkit-box-flex: 1;
-webkit-flex: 1;
-moz-box-flex: 1;
-moz-flex: 1;
-ms-flex: 1;
flex: 1;
display: block;
padding: 5px;
width: 100%; }
/* Vertically Align Columns */
/* .row-* vertically aligns every .col in the .row */
.row-top {
-webkit-box-align: start;
-ms-flex-align: start;
-webkit-align-items: flex-start;
-moz-align-items: flex-start;
align-items: flex-start; }
.row-bottom {
-webkit-box-align: end;
-ms-flex-align: end;
-webkit-align-items: flex-end;
-moz-align-items: flex-end;
align-items: flex-end; }
.row-center {
-webkit-box-align: center;
-ms-flex-align: center;
-webkit-align-items: center;
-moz-align-items: center;
align-items: center; }
.row-stretch {
-webkit-box-align: stretch;
-ms-flex-align: stretch;
-webkit-align-items: stretch;
-moz-align-items: stretch;
align-items: stretch; }
.row-baseline {
-webkit-box-align: baseline;
-ms-flex-align: baseline;
-webkit-align-items: baseline;
-moz-align-items: baseline;
align-items: baseline; }
/* .col-* vertically aligns an individual .col */
.col-top {
-webkit-align-self: flex-start;
-moz-align-self: flex-start;
-ms-flex-item-align: start;
align-self: flex-start; }
.col-bottom {
-webkit-align-self: flex-end;
-moz-align-self: flex-end;
-ms-flex-item-align: end;
align-self: flex-end; }
.col-center {
-webkit-align-self: center;
-moz-align-self: center;
-ms-flex-item-align: center;
align-self: center; }
/* Column Offsets */
.col-offset-10 {
margin-left: 10%; }
.col-offset-20 {
margin-left: 20%; }
.col-offset-25 {
margin-left: 25%; }
.col-offset-33, .col-offset-34 {
margin-left: 33.3333%; }
.col-offset-50 {
margin-left: 50%; }
.col-offset-66, .col-offset-67 {
margin-left: 66.6666%; }
.col-offset-75 {
margin-left: 75%; }
.col-offset-80 {
margin-left: 80%; }
.col-offset-90 {
margin-left: 90%; }
/* Explicit Column Percent Sizes */
/* By default each grid column will evenly distribute */
/* across the grid. However, you can specify individual */
/* columns to take up a certain size of the available area */
.col-10 {
-webkit-box-flex: 0;
-webkit-flex: 0 0 10%;
-moz-box-flex: 0;
-moz-flex: 0 0 10%;
-ms-flex: 0 0 10%;
flex: 0 0 10%;
max-width: 10%; }
.col-20 {
-webkit-box-flex: 0;
-webkit-flex: 0 0 20%;
-moz-box-flex: 0;
-moz-flex: 0 0 20%;
-ms-flex: 0 0 20%;
flex: 0 0 20%;
max-width: 20%; }
.col-25 {
-webkit-box-flex: 0;
-webkit-flex: 0 0 25%;
-moz-box-flex: 0;
-moz-flex: 0 0 25%;
-ms-flex: 0 0 25%;
flex: 0 0 25%;
max-width: 25%; }
.col-33, .col-34 {
-webkit-box-flex: 0;
-webkit-flex: 0 0 33.3333%;
-moz-box-flex: 0;
-moz-flex: 0 0 33.3333%;
-ms-flex: 0 0 33.3333%;
flex: 0 0 33.3333%;
max-width: 33.3333%; }
.col-40 {
-webkit-box-flex: 0;
-webkit-flex: 0 0 40%;
-moz-box-flex: 0;
-moz-flex: 0 0 40%;
-ms-flex: 0 0 40%;
flex: 0 0 40%;
max-width: 40%; }
.col-50 {
-webkit-box-flex: 0;
-webkit-flex: 0 0 50%;
-moz-box-flex: 0;
-moz-flex: 0 0 50%;
-ms-flex: 0 0 50%;
flex: 0 0 50%;
max-width: 50%; }
.col-60 {
-webkit-box-flex: 0;
-webkit-flex: 0 0 60%;
-moz-box-flex: 0;
-moz-flex: 0 0 60%;
-ms-flex: 0 0 60%;
flex: 0 0 60%;
max-width: 60%; }
.col-66, .col-67 {
-webkit-box-flex: 0;
-webkit-flex: 0 0 66.6666%;
-moz-box-flex: 0;
-moz-flex: 0 0 66.6666%;
-ms-flex: 0 0 66.6666%;
flex: 0 0 66.6666%;
max-width: 66.6666%; }
.col-75 {
-webkit-box-flex: 0;
-webkit-flex: 0 0 75%;
-moz-box-flex: 0;
-moz-flex: 0 0 75%;
-ms-flex: 0 0 75%;
flex: 0 0 75%;
max-width: 75%; }
.col-80 {
-webkit-box-flex: 0;
-webkit-flex: 0 0 80%;
-moz-box-flex: 0;
-moz-flex: 0 0 80%;
-ms-flex: 0 0 80%;
flex: 0 0 80%;
max-width: 80%; }
.col-90 {
-webkit-box-flex: 0;
-webkit-flex: 0 0 90%;
-moz-box-flex: 0;
-moz-flex: 0 0 90%;
-ms-flex: 0 0 90%;
flex: 0 0 90%;
max-width: 90%; }
/* Responsive Grid Classes */
/* Adding a class of responsive-X to a row */
/* will trigger the flex-direction to */
/* change to column and add some margin */
/* to any columns in the row for clearity */
@media (max-width: 567px) {
.responsive-sm {
-webkit-box-direction: normal;
-moz-box-direction: normal;
-webkit-box-orient: vertical;
-moz-box-orient: vertical;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column; }
.responsive-sm .col, .responsive-sm .col-10, .responsive-sm .col-20, .responsive-sm .col-25, .responsive-sm .col-33, .responsive-sm .col-34, .responsive-sm .col-50, .responsive-sm .col-66, .responsive-sm .col-67, .responsive-sm .col-75, .responsive-sm .col-80, .responsive-sm .col-90 {
-webkit-box-flex: 1;
-webkit-flex: 1;
-moz-box-flex: 1;
-moz-flex: 1;
-ms-flex: 1;
flex: 1;
margin-bottom: 15px;
margin-left: 0;
max-width: 100%;
width: 100%; } }
@media (max-width: 767px) {
.responsive-md {
-webkit-box-direction: normal;
-moz-box-direction: normal;
-webkit-box-orient: vertical;
-moz-box-orient: vertical;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column; }
.responsive-md .col, .responsive-md .col-10, .responsive-md .col-20, .responsive-md .col-25, .responsive-md .col-33, .responsive-md .col-34, .responsive-md .col-50, .responsive-md .col-66, .responsive-md .col-67, .responsive-md .col-75, .responsive-md .col-80, .responsive-md .col-90 {
-webkit-box-flex: 1;
-webkit-flex: 1;
-moz-box-flex: 1;
-moz-flex: 1;
-ms-flex: 1;
flex: 1;
margin-bottom: 15px;
margin-left: 0;
max-width: 100%;
width: 100%; } }
@media (max-width: 1023px) {
.responsive-lg {
-webkit-box-direction: normal;
-moz-box-direction: normal;
-webkit-box-orient: vertical;
-moz-box-orient: vertical;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column; }
.responsive-lg .col, .responsive-lg .col-10, .responsive-lg .col-20, .responsive-lg .col-25, .responsive-lg .col-33, .responsive-lg .col-34, .responsive-lg .col-50, .responsive-lg .col-66, .responsive-lg .col-67, .responsive-lg .col-75, .responsive-lg .col-80, .responsive-lg .col-90 {
-webkit-box-flex: 1;
-webkit-flex: 1;
-moz-box-flex: 1;
-moz-flex: 1;
-ms-flex: 1;
flex: 1;
margin-bottom: 15px;
margin-left: 0;
max-width: 100%;
width: 100%; } }
/**
* Utility Classes
* --------------------------------------------------
*/
.hide {
display: none; }
.opacity-hide {
opacity: 0; }
.grade-b .opacity-hide,
.grade-c .opacity-hide {
opacity: 1;
display: none; }
.show {
display: block; }
.opacity-show {
opacity: 1; }
.invisible {
visibility: hidden; }
.keyboard-open .hide-on-keyboard-open {
display: none; }
.keyboard-open .tabs.hide-on-keyboard-open + .pane .has-tabs,
.keyboard-open .bar-footer.hide-on-keyboard-open + .pane .has-footer {
bottom: 0; }
.inline {
display: inline-block; }
.disable-pointer-events {
pointer-events: none; }
.enable-pointer-events {
pointer-events: auto; }
.disable-user-behavior {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-touch-callout: none;
-webkit-tap-highlight-color: transparent;
-webkit-tap-highlight-color: transparent;
-webkit-user-drag: none;
-ms-touch-action: none;
-ms-content-zooming: none; }
.click-block {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
opacity: 0;
z-index: 99999;
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
overflow: hidden; }
.click-block-hide {
-webkit-transform: translate3d(-9999px, 0, 0);
transform: translate3d(-9999px, 0, 0); }
.no-resize {
resize: none; }
.block {
display: block;
clear: both; }
.block:after {
display: block;
visibility: hidden;
clear: both;
height: 0;
content: "."; }
.full-image {
width: 100%; }
.clearfix {
*zoom: 1; }
.clearfix:before, .clearfix:after {
display: table;
content: "";
line-height: 0; }
.clearfix:after {
clear: both; }
/**
* Content Padding
* --------------------------------------------------
*/
.padding {
padding: 10px; }
.padding-top,
.padding-vertical {
padding-top: 10px; }
.padding-right,
.padding-horizontal {
padding-right: 10px; }
.padding-bottom,
.padding-vertical {
padding-bottom: 10px; }
.padding-left,
.padding-horizontal {
padding-left: 10px; }
/**
* Scrollable iFrames
* --------------------------------------------------
*/
.iframe-wrapper {
position: fixed;
-webkit-overflow-scrolling: touch;
overflow: scroll; }
.iframe-wrapper iframe {
height: 100%;
width: 100%; }
/**
* Rounded
* --------------------------------------------------
*/
.rounded {
border-radius: 4px; }
/**
* Utility Colors
* --------------------------------------------------
* Utility colors are added to help set a naming convention. You'll
* notice we purposely do not use words like "red" or "blue", but
* instead have colors which represent an emotion or generic theme.
*/
.light, a.light {
color: #fff; }
.light-bg {
background-color: #fff; }
.light-border {
border-color: #ddd; }
.stable, a.stable {
color: #f8f8f8; }
.stable-bg {
background-color: #f8f8f8; }
.stable-border {
border-color: #b2b2b2; }
.positive, a.positive {
color: #387ef5; }
.positive-bg {
background-color: #387ef5; }
.positive-border {
border-color: #0c60ee; }
.calm, a.calm {
color: #11c1f3; }
.calm-bg {
background-color: #11c1f3; }
.calm-border {
border-color: #0a9dc7; }
.assertive, a.assertive {
color: #ef473a; }
.assertive-bg {
background-color: #ef473a; }
.assertive-border {
border-color: #e42112; }
.balanced, a.balanced {
color: #33cd5f; }
.balanced-bg {
background-color: #33cd5f; }
.balanced-border {
border-color: #28a54c; }
.energized, a.energized {
color: #ffc900; }
.energized-bg {
background-color: #ffc900; }
.energized-border {
border-color: #e6b500; }
.royal, a.royal {
color: #886aea; }
.royal-bg {
background-color: #886aea; }
.royal-border {
border-color: #6b46e5; }
.dark, a.dark {
color: #444; }
.dark-bg {
background-color: #444; }
.dark-border {
border-color: #111; }
[collection-repeat] {
/* Position is set by transforms */
left: 0 !important;
top: 0 !important;
position: absolute !important;
z-index: 1; }
.collection-repeat-container {
position: relative;
z-index: 1; }
.collection-repeat-after-container {
z-index: 0;
display: block;
/* when scrolling horizontally, make sure the after container doesn't take up 100% width */ }
.collection-repeat-after-container.horizontal {
display: inline-block; }
[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak,
.x-ng-cloak, .ng-hide:not(.ng-hide-animate) {
display: none !important; }
/**
* Platform
* --------------------------------------------------
* Platform specific tweaks
*/
.platform-ios.platform-cordova:not(.fullscreen) .bar-header:not(.bar-subheader) {
height: 64px; }
.platform-ios.platform-cordova:not(.fullscreen) .bar-header:not(.bar-subheader).item-input-inset .item-input-wrapper {
margin-top: 19px !important; }
.platform-ios.platform-cordova:not(.fullscreen) .bar-header:not(.bar-subheader) > * {
margin-top: 20px; }
.platform-ios.platform-cordova:not(.fullscreen) .tabs-top > .tabs,
.platform-ios.platform-cordova:not(.fullscreen) .tabs.tabs-top {
top: 64px; }
.platform-ios.platform-cordova:not(.fullscreen) .has-header,
.platform-ios.platform-cordova:not(.fullscreen) .bar-subheader {
top: 64px; }
.platform-ios.platform-cordova:not(.fullscreen) .has-subheader {
top: 108px; }
.platform-ios.platform-cordova:not(.fullscreen) .has-header.has-tabs-top {
top: 113px; }
.platform-ios.platform-cordova:not(.fullscreen) .has-header.has-subheader.has-tabs-top {
top: 157px; }
.platform-ios.platform-cordova .popover .bar-header:not(.bar-subheader) {
height: 44px; }
.platform-ios.platform-cordova .popover .bar-header:not(.bar-subheader).item-input-inset .item-input-wrapper {
margin-top: -1px; }
.platform-ios.platform-cordova .popover .bar-header:not(.bar-subheader) > * {
margin-top: 0; }
.platform-ios.platform-cordova .popover .has-header,
.platform-ios.platform-cordova .popover .bar-subheader {
top: 44px; }
.platform-ios.platform-cordova .popover .has-subheader {
top: 88px; }
.platform-ios.platform-cordova.status-bar-hide {
margin-bottom: 20px; }
@media (orientation: landscape) {
.platform-ios.platform-browser.platform-ipad {
position: fixed; } }
.platform-c:not(.enable-transitions) * {
-webkit-transition: none !important;
transition: none !important; }
.slide-in-up {
-webkit-transform: translate3d(0, 100%, 0);
transform: translate3d(0, 100%, 0); }
.slide-in-up.ng-enter,
.slide-in-up > .ng-enter {
-webkit-transition: all cubic-bezier(0.1, 0.7, 0.1, 1) 400ms;
transition: all cubic-bezier(0.1, 0.7, 0.1, 1) 400ms; }
.slide-in-up.ng-enter-active,
.slide-in-up > .ng-enter-active {
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0); }
.slide-in-up.ng-leave,
.slide-in-up > .ng-leave {
-webkit-transition: all ease-in-out 250ms;
transition: all ease-in-out 250ms; }
@-webkit-keyframes scaleOut {
from {
-webkit-transform: scale(1);
opacity: 1; }
to {
-webkit-transform: scale(0.8);
opacity: 0; } }
@keyframes scaleOut {
from {
transform: scale(1);
opacity: 1; }
to {
transform: scale(0.8);
opacity: 0; } }
@-webkit-keyframes superScaleIn {
from {
-webkit-transform: scale(1.2);
opacity: 0; }
to {
-webkit-transform: scale(1);
opacity: 1; } }
@keyframes superScaleIn {
from {
transform: scale(1.2);
opacity: 0; }
to {
transform: scale(1);
opacity: 1; } }
[nav-view-transition="ios"] [nav-view="entering"],
[nav-view-transition="ios"] [nav-view="leaving"] {
-webkit-transition-duration: 500ms;
transition-duration: 500ms;
-webkit-transition-timing-function: cubic-bezier(0.36, 0.66, 0.04, 1);
transition-timing-function: cubic-bezier(0.36, 0.66, 0.04, 1);
-webkit-transition-property: opacity, -webkit-transform, box-shadow;
transition-property: opacity, transform, box-shadow; }
[nav-view-transition="ios"][nav-view-direction="forward"], [nav-view-transition="ios"][nav-view-direction="back"] {
background-color: #000; }
[nav-view-transition="ios"] [nav-view="active"],
[nav-view-transition="ios"][nav-view-direction="forward"] [nav-view="entering"],
[nav-view-transition="ios"][nav-view-direction="back"] [nav-view="leaving"] {
z-index: 3; }
[nav-view-transition="ios"][nav-view-direction="back"] [nav-view="entering"],
[nav-view-transition="ios"][nav-view-direction="forward"] [nav-view="leaving"] {
z-index: 2; }
[nav-bar-transition="ios"] .title,
[nav-bar-transition="ios"] .buttons,
[nav-bar-transition="ios"] .back-text {
-webkit-transition-duration: 500ms;
transition-duration: 500ms;
-webkit-transition-timing-function: cubic-bezier(0.36, 0.66, 0.04, 1);
transition-timing-function: cubic-bezier(0.36, 0.66, 0.04, 1);
-webkit-transition-property: opacity, -webkit-transform;
transition-property: opacity, transform; }
[nav-bar-transition="ios"] [nav-bar="active"],
[nav-bar-transition="ios"] [nav-bar="entering"] {
z-index: 10; }
[nav-bar-transition="ios"] [nav-bar="active"] .bar,
[nav-bar-transition="ios"] [nav-bar="entering"] .bar {
background: transparent; }
[nav-bar-transition="ios"] [nav-bar="cached"] {
display: block; }
[nav-bar-transition="ios"] [nav-bar="cached"] .header-item {
display: none; }
[nav-view-transition="android"] [nav-view="entering"],
[nav-view-transition="android"] [nav-view="leaving"] {
-webkit-transition-duration: 200ms;
transition-duration: 200ms;
-webkit-transition-timing-function: cubic-bezier(0.4, 0.6, 0.2, 1);
transition-timing-function: cubic-bezier(0.4, 0.6, 0.2, 1);
-webkit-transition-property: -webkit-transform;
transition-property: transform; }
[nav-view-transition="android"] [nav-view="active"],
[nav-view-transition="android"][nav-view-direction="forward"] [nav-view="entering"],
[nav-view-transition="android"][nav-view-direction="back"] [nav-view="leaving"] {
z-index: 3; }
[nav-view-transition="android"][nav-view-direction="back"] [nav-view="entering"],
[nav-view-transition="android"][nav-view-direction="forward"] [nav-view="leaving"] {
z-index: 2; }
[nav-bar-transition="android"] .title,
[nav-bar-transition="android"] .buttons {
-webkit-transition-duration: 200ms;
transition-duration: 200ms;
-webkit-transition-timing-function: cubic-bezier(0.4, 0.6, 0.2, 1);
transition-timing-function: cubic-bezier(0.4, 0.6, 0.2, 1);
-webkit-transition-property: opacity;
transition-property: opacity; }
[nav-bar-transition="android"] [nav-bar="active"],
[nav-bar-transition="android"] [nav-bar="entering"] {
z-index: 10; }
[nav-bar-transition="android"] [nav-bar="active"] .bar,
[nav-bar-transition="android"] [nav-bar="entering"] .bar {
background: transparent; }
[nav-bar-transition="android"] [nav-bar="cached"] {
display: block; }
[nav-bar-transition="android"] [nav-bar="cached"] .header-item {
display: none; }
[nav-swipe="fast"] [nav-view],
[nav-swipe="fast"] .title,
[nav-swipe="fast"] .buttons,
[nav-swipe="fast"] .back-text {
-webkit-transition-duration: 50ms;
transition-duration: 50ms;
-webkit-transition-timing-function: linear;
transition-timing-function: linear; }
[nav-swipe="slow"] [nav-view],
[nav-swipe="slow"] .title,
[nav-swipe="slow"] .buttons,
[nav-swipe="slow"] .back-text {
-webkit-transition-duration: 160ms;
transition-duration: 160ms;
-webkit-transition-timing-function: linear;
transition-timing-function: linear; }
[nav-view="cached"],
[nav-bar="cached"] {
display: none; }
[nav-view="stage"] {
opacity: 0;
-webkit-transition-duration: 0;
transition-duration: 0; }
[nav-bar="stage"] .title,
[nav-bar="stage"] .buttons,
[nav-bar="stage"] .back-text {
position: absolute;
opacity: 0;
-webkit-transition-duration: 0s;
transition-duration: 0s; }
.item-image i:last-child {
position: absolute;
border-radius: 50%;
width: 20px;
height: 20px;
background-color: black;
top: 15px;
right: 15px; }
.link-span {
cursor: pointer;
color: #387ef5; }
.center {
margin-left: auto;
margin-right: auto;
display: block; }
p ul {
list-style-type: disc !important;
list-style-position: inside !important; }
================================================
FILE: mobile/www/css/style.css
================================================
/* General classes */
.pull-right {
float:right;
}
.clearfix {
clear:both;
}
.subheading, .date {
font-size:12px;
font-style:italic;
}
.center {
margin-left: auto;
margin-right: auto;
display: block;
}
/* More specific to sections of app */
.profile-name .subheading, .profile-name .date {
vertical-align: top;
}
div .tatami-avatar {
min-height: 42px;
}
.tatami-attached-image{
height: auto;
max-width: 350px;
}
.bar .bar-header .title {
left:0!important;
right:0!important;
}
.bar .bar-header .title a {
text-decoration: none;
}
.bar .bar-header .title img {
margin: 0 auto;
vertical-align: middle;
}
/* Platform specific styles */
.platform-ios .tab-nav.tabs {
bottom:43px;
}
.platform-ios .bar.bar-footer {
bottom: 43px; !important;
}
.platform-ios .tatami-header {
top: 64px;
}
.platform-ios .tatami-footer {
bottom: 88px;
}
.platform-android .tatami-header {
top: 88px;
}
.platform-android .tatami-footer {
bottom: 44px;
}
.v-align {
vertical-align: middle !important;
}
================================================
FILE: mobile/www/i18n/en/conversation.json
================================================
{
"conversation": {
"title": "Conversation"
}
}
================================================
FILE: mobile/www/i18n/en/follow.json
================================================
{
"tab": {
"follower": "Followers",
"following": "Following",
"suggested": {
"title": "Suggested",
"who": "Who to Follow"
}
}
}
================================================
FILE: mobile/www/i18n/en/home.json
================================================
{
"tab": {
"timeline": "Timeline",
"mentions": "Mentions",
"favorites": "Favorites",
"more": "More"
},
"noContent" : "No content found"
}
================================================
FILE: mobile/www/i18n/en/login.json
================================================
{
"login": {
"title": "Login",
"email": "E-mail",
"password": "Password",
"google": "Google Login",
"success": "Logged In!",
"failed": "Failed to log in!",
"progress": "Login in progress...",
"googleUnavaible": "Google Login unavailable",
"tos": "Terms of Service",
"accept": "Accept",
"changeServer": "Change Tatami server",
"endpoint": {
"current": "Current Server:"
}
}
}
================================================
FILE: mobile/www/i18n/en/more.json
================================================
{
"more": {
"title": "More Options",
"company": "Company Timeline",
"settings": "Settings",
"blockedUsers": {
"title": "Blocked Users",
"no": "You don't have any blocked users"
},
"reportedStatus":{
"title": "Reported Statuses",
"no": "You don't have any reported statuses"
},
"logout": "Logout",
"language": {
"change": "Language",
"english": "English",
"french": "French"
},
"allUsers": "All Users"
}
}
================================================
FILE: mobile/www/i18n/en/post.json
================================================
{
"post": {
"title": "Post",
"message": "Post Here...",
"progress": "Post in progress...",
"error": {
"message": "Something went wrong. Returning to Timeline",
"truncated": "Your post has been truncated to remain under the max length."
},
"location": {
"share": "You will share your location",
"unshare": "You won't share your location",
"fail": "Location failure. Please check that your GPS is enabled on your device"
},
"private": {
"yes": "Your message will be private",
"no": "Your message will be public"
}
}
}
================================================
FILE: mobile/www/i18n/en/server.json
================================================
{
"server": {
"success": "Logged In!",
"failed": "Failed to log in!",
"progress": "Login in progress...",
"endpoint": {
"title": "Server",
"current": "Current Server:",
"change": "Change Server",
"error": {
"title": "Error",
"body": "Failed to change endpoint - cannot reach server."
},
"authenticate": {
"title": "Success",
"body": "Endpoint changed - please reauthenticate with the new server."
},
"default": "Use default server"
}
}
}
================================================
FILE: mobile/www/i18n/en/status.json
================================================
{
"status": {
"share": {
"title": "shared",
"mention": "Your status has been shared",
"toast": "You have shared this status",
"action": "Share status"
},
"reply": "In reply to ",
"follower": "New follower",
"delete": "Are you sure you want to delete this status?",
"report" : "Report",
"reportMessage": "This status has been reported",
"reportStatus":{
"message": "Are you sure you want to report this status? This action cannot be undone.",
"toast": "Status has been reported",
"approve": "Are you sure you approve this status? It will continue to be displayed on the Company Timeline.",
"approveToast": "Status has been approved",
"delete": "Are you sure you want to delete this status? This action cannot be undone.",
"deleteToast": "Status has been deleted"
},
"block": {
"reportStatus": "Report status",
"blockUser": "Block user",
"delete": "Delete"
},
"announcement": {
"title": "announced",
"mention": "Your status has been announced",
"toast": "You have announced this status",
"action": "Announce status"
},
"hide": {
"action": "Hide status",
"toast": "This status has been removed from your timeline"
},
"favorite": {
"add": "This status has been added to your favorites",
"remove": "This status has been removed from your favorites"
},
"private": "Private message",
"noContent" : "No content found"
}
}
================================================
FILE: mobile/www/i18n/en/user.json
================================================
{
"user": {
"profile": {
"title": "Profile",
"options": {
"block": "Block User",
"deactivate": "Deactivate User",
"reactivate": "Reactivate User"
}
},
"follow": "Follow",
"unfollow": "Unfollow",
"block":{
"success": "You have blocked this user",
"title": "Block",
"confirmation": "Are you sure you want to block this user? You won't be able to see their posts anymore."
} ,
"unblock": {
"success": "You have unblocked this user",
"title": "Unblock"
},
"reactivate": {
"title": "Reactivate",
"confirmation": "Are you sure you want to reactivate this user?"
},
"deactivate": {
"title": "Deactivate",
"confirmation": "Are you sure you want to deactivate this user? They won't be able to log in to Tatami."
}
}
}
================================================
FILE: mobile/www/i18n/fr/conversation.json
================================================
{
"conversation": {
"title": "Conversation"
}
}
================================================
FILE: mobile/www/i18n/fr/follow.json
================================================
{
"tab": {
"follower": "Abonnés",
"following": "Abonnement",
"suggested": {
"title": "Recommandé",
"who": "Qui suivre"
}
}
}
================================================
FILE: mobile/www/i18n/fr/home.json
================================================
{
"tab": {
"timeline": "Timeline",
"mentions": "Mentions",
"favorites": "Favoris",
"more": "Plus"
},
"noContent" : "Cette section est actuellement vide"
}
================================================
FILE: mobile/www/i18n/fr/login.json
================================================
{
"login": {
"title": "Login",
"email": "E-mail",
"password": "Mot de passe",
"google": "Google Login",
"success": "Connexion effectuée !",
"failed": "Echec de connexion",
"progress": "Connexion en cours...",
"googleUnavaible": "Google Login indisponible",
"tos": "conditions d'utilisation",
"accept": "Accepter les",
"changeServer": "Changer le serveur Tatami",
"endpoint": {
"current": "Serveur Actuel:"
}
}
}
================================================
FILE: mobile/www/i18n/fr/more.json
================================================
{
"more": {
"title": "Plus d'options",
"company": "Timeline de l'entreprise",
"settings": "Paramètres",
"blockedUsers": {
"title": "Gérer vos utilisateurs bloqués",
"no": "Vous n'avez bloqué aucun utilisateur"
},
"reportedStatus": {
"title": "Statuts signalés",
"no": "Aucun statut n'a été signalé"
},
"logout": "Déconnexion",
"language": {
"change": "Langue",
"english": "Anglais",
"french": "Français"
},
"allUsers": "Tous les utilisateurs"
}
}
================================================
FILE: mobile/www/i18n/fr/post.json
================================================
{
"post": {
"title": "Post",
"message": "Ecrivez votre message ici...",
"progress": "Publication en cours...",
"error": {
"message": "Un problème est survenu. Retour à la Timeline.",
"truncated": "Votre message a été tronqué afin de respecter la longueur maximale autorisée"
},
"location": {
"share": "Votre position sera partagée",
"unshare": "Votre position ne sera pas partagée",
"fail": "Echec de la localisation. Vérifiez que le GPS de votre téléphone est activé"
},
"private": {
"yes": "Votre message sera privé",
"no": "Votre message sera public"
}
}
}
================================================
FILE: mobile/www/i18n/fr/server.json
================================================
{
"server": {
"success": "Connexion effectuée !",
"failed": "Echec de connexion",
"progress": "Connexion en cours...",
"endpoint": {
"title": "Serveur",
"current": "Serveur Actuel:",
"change": "Changer de serveur",
"error": {
"title": "Échec de la modification du serveur",
"body": "Serveur inaccessible."
},
"authenticate": {
"title": "Changement de serveur effectué",
"body": "Veuillez-vous identifier avec le nouveau serveur."
},
"default": "Utiliser le serveur par défaut"
}
}
}
================================================
FILE: mobile/www/i18n/fr/status.json
================================================
{
"status": {
"share": {
"title": "a partagé",
"mention": "Votre statut a été partagé",
"toast": "Vous avez partagé ce statut",
"action": "Partager"
},
"reply": "En réponse à ",
"follower": "Nouvel abonné",
"delete": "Êtes-vous sûr de vouloir supprimer ce statut ?",
"report" : "Supprimer",
"reportMessage": "Ce statut a été signalé",
"reportStatus":{
"message": "Êtes-vous sûr de vouloir signaler ce statut?",
"toast": "Vous avez signalé ce statut",
"approve": "Êtes-vous sûr de vouloir approuver cette publication ? Cela ne modifiera pas son apparition sur Tatami.",
"approveToast": "Vous avez approuvé ce statut",
"delete": "Êtes-vous sûr de vouloir supprimer ce statut ? Cette action est irréversible.",
"deleteToast": "Vous avez supprimé ce statut"
},
"block": {
"reportStatus": "Signaler ce statut",
"blockUser": "Bloquer cet utilisateur",
"delete": "Supprimer"
},
"announcement": {
"title": "a annoncé",
"mention": "Votre status a été annoncé",
"toast": "Vous avez annoncé ce statut",
"action": "Annoncer"
},
"hide": {
"action": "Cacher ce statut",
"toast": "Ce statut a été supprimé de votre timeline"
},
"favorite": {
"add": "Ce statut a été ajouté à vos favoris",
"remove": "Ce statut a été supprimé de vos favoris"
},
"private": "Message privé",
"noContent" : "Cette section est actuellement vide"
}
}
================================================
FILE: mobile/www/i18n/fr/user.json
================================================
{
"user": {
"profile": {
"title": "Profil",
"options": {
"block": "Bloquer cet utilisateur",
"deactivate": "Désactiver cet utilisateur",
"reactivate": "Réactiver cet utilisateur"
}
},
"follow": "S'abonner",
"unfollow": "Se désabonner",
"block":{
"success": "Vous avez bloqué cet utilisateur",
"title": "Bloquer",
"confirmation": "Êtes-vous sûr de vouloir bloquer cet utilisateur ? Vous ne verrez plus ses publications."
} ,
"unblock": {
"success": "Vous avez débloqué cet utilisateur.",
"title": "Débloquer"
},
"reactivate": {
"title": "Réactiver",
"confirmation": "Êtes-vous sûr de vouloir réactiver cet utilisateur ?"
},
"deactivate": {
"title": "Désactiver",
"confirmation": "Êtes-vous sûr de vouloir désactiver cet utilisateur ? Il ne pourra plus se connecter à Tatami."
}
}
}
================================================
FILE: mobile/www/index.html
================================================
================================================
FILE: mobile/www/test/javascript/components/profile/profile.controller.spec.js
================================================
describe("License controller test", function() {
beforeEach(inject(function(_$controller_) {
$controller = _$controller_;
}));
it('SANITY', function(){
expect(true).toBeTruthy();
});
});
================================================
FILE: pom.xml
================================================
4.0.0
fr.ippon.tatami
tatami
4.0.5
web
services
tatamibot
mobile
pom
Tatami is an enterprise micro-blogging platform
2012
https://github.com/ippontech/tatami
Ippon Technologies
http://www.ippon.fr
Apache License, Version 2.0
http://www.apache.org/licenses/LICENSE-2.0
repo
GitHub
https://github.com/ippontech/tatami/issues
http://github.com/ippontech/tatami
scm:git:git@github.com:ippontech/tatami.git
default
true
elasticsearch-remote
remote
target/elasticsearch
target/elasticsearch/log
true
preprod
apple-push
0
DEBUG
PAPERTRAIL
logs.papertrailapp.com
38143
128M
http://sandbox.tatamisoft.com
localhost
25
tatami@ippon.fr
true
false
prod
80
0
INFO
PAPERTRAIL
logs.papertrailapp.com
38143
512M
http://tatami.ippon.fr
ldap://10.55.0.4:389
localhost
25
tatami@ippon.fr
UA-10959780-3
true
false
https
embedded
/opt/tatami/data/elasticsearch
/opt/tatami/log/elasticsearch
false
false
2003
org.mortbay.jetty
jetty-maven-plugin
${maven.jetty.version}
${jetty.scanIntervalSeconds}
stop-jetty
9999
jetty.port
${jetty.port}
start-jetty
pre-integration-test
run
0
true
stop-jetty
post-integration-test
stop
org.codehaus.mojo
exec-maven-plugin
1.2.1
insert-version
generate-sources
scripts/insertBuildVersion.sh
${project.version}
${buildNumber}
exec
stress-tests
0
WARN
CONSOLE
64M
true
uitest
services/test/resources
src/integration/resources
org.codehaus.mojo
build-helper-maven-plugin
1.4
add-uitests-source
add-test-source
${basedir}/src/integration/java
org.codehaus.gmaven
gmaven-plugin
1.4
1.8
${basedir}/src/integration/java
**/*.groovy
testCompile
org.apache.maven.plugins
maven-failsafe-plugin
2.12.3
${basedir}/src/integration/java
**/*Spec.*
target/test-reports/geb
${webdriver.chrome.driver}
${google.password}
${google.email}
integration-test
verify
org.codehaus.mojo
cassandra-maven-plugin
${maven.cassandra.version}
start-cassandra
pre-integration-test
start
org.codehaus.groovy
groovy-all
1.8.1
org.spockframework
spock-core
0.6-groovy-1.8
test
org.codehaus.geb
geb-spock
0.7.1
test
org.seleniumhq.selenium
selenium-firefox-driver
${selenium.version}
test
org.seleniumhq.selenium
selenium-htmlunit-driver
${selenium.version}
test
org.seleniumhq.selenium
selenium-chrome-driver
${selenium.version}
test
org.seleniumhq.selenium
selenium-support
${selenium.version}
test
org.apache.directory.server
apacheds-all
1.5.5
test
org.apache.directory.shared
shared-ldap
8080
1
DEBUG
CONSOLE
localhost
64M
${project.version}
http://localhost:8080
ldap://directory:389
dc=ippon,dc=fr
(uid={0})
tatami@localhost
false
true
any
embedded
target/elasticsearch
target/elasticsearch/log
true
false
2003
2592000
${project.basedir}/node_modules/.bin
${project.build.directory}/test-results
reuseReports
jacoco
${project.testresult.directory}/surefire-reports
${project.testresult.directory}/coverage/jacoco/jacoco.exec
${project.testresult.directory}/coverage/jacoco/jacoco-it.exec
${project.basedir}/src/main/
${project.basedir}/src/test/
${project.testresult.directory}/coverage/report-lcov/lcov.info
${project.testresult.directory}/karma
src/main/webapp/assets/**.*
UTF-8
1.6
3.2.3.RELEASE
3.1.3.RELEASE
0.20.6
1.6.11
1.7.5
1.0.13
4.2.2
4.2.2
1.2.6
1.1-4
3.1-09
1.0.2
1
3.0.1
1.0.0.GA
4.3.0.Final
1.2
2.1.2
2.1
2.6
2.3
4.11
1.3
1.3.5
1.2.0.1
1.9.0
1.0
1.2.1
1.7
2.2.0
2.11.0
2.31.0
3.0
1.2
2.14
2.3
8.1.13.v20130916
1.2.1-1
1.7.0
2.9
Tomcat
sonatype-releases
Sonatype Releases Repository
http://oss.sonatype.org/content/repositories/releases/
sonatype-nexus-snapshots
Sonatype Nexus Snapshots
https://oss.sonatype.org/content/repositories/snapshots
false
true
3.1.0
com.google.api-client
google-api-client
1.20.0
com.google.apis
google-api-services-plus
v1-rev345-1.21.0
com.fasterxml.jackson.datatype
jackson-datatype-json-org
${jackson.version}
com.fasterxml.jackson.datatype
jackson-datatype-hppc
${jackson.version}
com.fasterxml.jackson.datatype
jackson-datatype-joda
${jackson.version}
com.notnoop.apns
apns
0.2.3
com.yammer.metrics
metrics-core
${yammer.metrics.version}
com.yammer.metrics
metrics-ehcache
${yammer.metrics.version}
com.yammer.metrics
metrics-graphite
${yammer.metrics.version}
com.yammer.metrics
metrics-servlet
${yammer.metrics.version}
com.yammer.metrics
metrics-spring
${yammer.metrics.version}
com.yammer.metrics
metrics-web
${yammer.metrics.version}
commons-fileupload
commons-fileupload
1.2.2
commons-io
commons-io
${commons.io.version}
commons-lang
commons-lang
${commons.lang.version}
javax.inject
javax.inject
${javax.inject.version}
javax.persistence
persistence-api
${persistence.api.version}
javax.servlet
javax.servlet-api
${javax.servlet.api.version}
provided
javax.servlet
jstl
${jstl.version}
javax.validation
validation-api
${javax.validation.api.version}
joda-time
joda-time
${jodatime.version}
ch.qos.logback
logback-core
${logback.version}
ch.qos.logback
logback-classic
${logback.version}
net.sf.ehcache
ehcache-core
2.6.5
net.sf.ehcache
ehcache-web
2.0.4
org.apache.camel
camel-core
${camel.version}
org.apache.camel
camel-spring
${camel.version}
org.apache.camel
camel-script
${camel.version}
org.apache.camel
camel-rss
${camel.version}
org.apache.camel
camel-twitter
${camel.version}
org.apache.camel
camel-stream
${camel.version}
org.apache.httpcomponents
httpcore
${httpclient.core.version}
org.apache.httpcomponents
httpclient
${httpclient.client.version}
org.apache.geronimo.javamail
geronimo-javamail_1.4_mail
1.8.2
org.apache.openjpa
openjpa
2.1.0
org.apache.velocity
velocity
${velocity.version}
org.aspectj
aspectjrt
${aspectj.version}
jar
compile
org.aspectj
aspectjweaver
${aspectj.version}
jar
compile
org.atmosphere
atmosphere-annotations
1.1.0.RC3
org.elasticsearch
elasticsearch
${elasticsearch.version}
org.hectorclient
hector-core
${hector.version}
javax.servlet
servlet-api
org.hectorclient
hector-object-mapper
${hector.mapper.version}
org.mortbay.jetty
servlet-api
org.hibernate
hibernate-validator
${hibernate.validator.version}
org.pegdown
pegdown
${pegdown.version}
org.slf4j
slf4j-api
${slf4j.version}
org.slf4j
jcl-over-slf4j
${slf4j.version}
org.springframework
spring-aop
${spring.version}
org.springframework
spring-beans
${spring.version}
org.springframework
spring-context
${spring.version}
org.springframework
spring-context-support
${spring.version}
org.springframework
spring-core
${spring.version}
org.springframework
spring-orm
${spring.version}
org.springframework
spring-tx
${spring.version}
org.springframework
spring-web
${spring.version}
org.springframework
spring-webmvc
${spring.version}
org.springframework.security
spring-security-core
${spring.security.version}
org.springframework.security
spring-security-config
${spring.security.version}
org.springframework.security
spring-security-crypto
${spring.security.version}
org.springframework.security
spring-security-ldap
${spring.security.version}
org.springframework.security
spring-security-openid
${spring.security.version}
org.springframework.security
spring-security-taglibs
${spring.security.version}
org.springframework.security
spring-security-web
${spring.security.version}
org.pac4j
pac4j-oauth
1.6.0
org.pac4j
spring-security-pac4j
1.2.4
rome
rome
${rome.version}
org.apache.cassandra
cassandra-all
${cassandra.version}
javax.servlet
servlet-api
org.mortbay.jetty
servlet-api
org.mortbay.jetty
jetty
org.mortbay.jetty
jetty-util
test
com.jayway.jsonpath
json-path
0.8.1
test
org.springframework
spring-test
${spring.version}
test
org.hamcrest
hamcrest-core
${hamcrest.version}
test
org.hamcrest
hamcrest-library
${hamcrest.version}
test
junit
junit
${junit.version}
test
com.jayway.awaitility
awaitility
${awaitility.version}
test
org.cassandraunit
cassandra-unit
${cassandra.unit.version}
test
hamcrest-all
org.hamcrest
org.mockito
mockito-all
${mockito.version}
test
org.apache.camel
camel-test
${camel.version}
test
root
src/main/resources
true
**/*
org.apache.maven.plugins
maven-compiler-plugin
${maven.compiler.version}
${java.version}
${java.version}
org.apache.maven.plugins
maven-enforcer-plugin
${maven.enforcer.version}
enforce-versions
enforce
[3.0.0,)
[1.6.0,)
org.codehaus.mojo
buildnumber-maven-plugin
1.1
7
validate
create
org.apache.maven.plugins
maven-surefire-plugin
${maven.surefire.version}
${project.testresult.directory}/surefire-reports
-XX:MaxPermSize=128m -Xmx256m ${surefireArgLine}
${skip.unit.tests}
alphabetical
org.codehaus.mojo
cassandra-maven-plugin
${maven.cassandra.version}
org.apache.cassandra
cassandra-all
${cassandra.version}
org.mortbay.jetty
jetty-maven-plugin
${maven.jetty.version}
${jetty.scanIntervalSeconds}
stop-jetty
9999
jetty.port
${jetty.port}
spring.profiles.active
${spring.profiles.active}
/
org.apache.tomcat.maven
tomcat7-maven-plugin
2.1
/
true
org.apache.coyote.http11.Http11NioProtocol
${spring.profiles.active}
org.apache.maven.plugins
maven-eclipse-plugin
${maven-eclipse-plugin.version}
true
true
.settings/org.eclipse.core.resources.prefs
=${project.build.sourceEncoding}${line.separator}]]>
org.codehaus.mojo
sonar-maven-plugin
2.5
org.jacoco
jacoco-maven-plugin
0.7.4.201502262128
pre-unit-tests
prepare-agent
${project.testresult.directory}/coverage/jacoco/jacoco.exec
surefireArgLine
post-unit-test
test
report
${project.testresult.directory}/coverage/jacoco/jacoco.exec
${project.testresult.directory}/coverage/jacoco
org.eclipse.m2e
lifecycle-mapping
1.0.0
ro.isdc.wro4j
wro4j-maven-plugin
[1.4.7,)
run
org.codehaus.gmaven
gmaven-plugin
[1.4,)
testCompile
================================================
FILE: scripts/insertBuildVersion.sh
================================================
#!/bin/bash
darwin=false;
case "`uname`" in
Darwin*) darwin=true ;;
esac
if $darwin; then
sedi="sed -i .sed.del"
else
sedi="sed -i"
fi
find . -name FooterView.html | xargs $sedi "s/version<\/pom>/$1/"
find . -name FooterView.html | xargs $sedi "s/build<\/pom>/$2/"
find . -name HomeSidebarView.html | xargs $sedi "s/version<\/pom>/$1/"
find . -name HomeSidebarView.html | xargs $sedi "s/build<\/pom>/$2/"
if $darwin; then
find . -name FooterView.html.sed.del | xargs rm
find . -name HomeSidebarView.html.sed.del | xargs rm
fi
================================================
FILE: services/pom.xml
================================================
tatami
fr.ippon.tatami
4.0.5
4.0.0
services
tatami-services-${project.version}
org.apache.maven.plugins
maven-jar-plugin
2.4
test-jar
org.apache.maven.plugins
maven-remote-resources-plugin
1.5
bundle
**/*.xml
**/tatami/**/*
================================================
FILE: services/src/main/java/fr/ippon/tatami/config/ApplicationConfiguration.java
================================================
package fr.ippon.tatami.config;
import org.apache.thrift.transport.TTransportException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.*;
import org.springframework.core.env.Environment;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import java.io.IOException;
@Configuration
@PropertySource({"classpath:/META-INF/tatami/tatami.properties",
"classpath:/META-INF/tatami/customization.properties"})
@ComponentScan(basePackages = {
"fr.ippon.tatami.repository",
"fr.ippon.tatami.service",
"fr.ippon.tatami.security"})
@Import(value = {
AsyncConfiguration.class,
CacheConfiguration.class,
CassandraConfiguration.class,
SearchConfiguration.class,
MailConfiguration.class,
MetricsConfiguration.class})
@ImportResource("classpath:META-INF/spring/applicationContext-*.xml")
public class ApplicationConfiguration {
private final Logger log = LoggerFactory.getLogger(ApplicationConfiguration.class);
@Inject
private Environment env;
/**
* Initializes Tatami.
*
* Spring profiles can be configured with a system property -Dspring.profiles.active=your-active-profile
*
* Available profiles are :
* - "apple-push" : for enabling Apple Push notifications
* - "metrics" : for enabling Yammer Metrics
* - "tatamibot" : for enabling the Tatami bot
*/
@PostConstruct
public void initTatami() throws IOException, TTransportException {
log.debug("Looking for Spring profiles... Available profiles are \"metrics\", \"tatamibot\" and \"apple-push\"");
if (env.getActiveProfiles().length == 0) {
log.debug("No Spring profile configured, running with default configuration");
} else {
for (String profile : env.getActiveProfiles()) {
log.debug("Detected Spring profile : " + profile);
}
}
Constants.VERSION = env.getRequiredProperty("tatami.version");
Constants.GOOGLE_ANALYTICS_KEY = env.getProperty("tatami.google.analytics.key");
log.info("Tatami v. {} started!", Constants.VERSION);
log.debug("Google Analytics key : {}", Constants.GOOGLE_ANALYTICS_KEY);
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/config/AsyncConfiguration.java
================================================
package fr.ippon.tatami.config;
import fr.ippon.tatami.service.SearchService;
import fr.ippon.tatami.service.elasticsearch.ElasticsearchSearchService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
@Configuration
@EnableAsync
@EnableScheduling
public class AsyncConfiguration implements AsyncConfigurer {
private final Logger log = LoggerFactory.getLogger(AsyncConfiguration.class);
@Bean
public SearchService searchService() {
return new ElasticsearchSearchService();
}
@Override
public Executor getAsyncExecutor() {
log.debug("Creating Async Task Executor");
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(50);
executor.setQueueCapacity(10000);
executor.setThreadNamePrefix("TatamiExecutor-");
executor.initialize();
return executor;
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/config/CacheConfiguration.java
================================================
package fr.ippon.tatami.config;
import com.yammer.metrics.ehcache.InstrumentedEhcache;
import net.sf.ehcache.Cache;
import net.sf.ehcache.Ehcache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.ehcache.EhCacheCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import javax.annotation.PreDestroy;
import javax.inject.Inject;
@Configuration
@EnableCaching
public class CacheConfiguration {
private final Logger log = LoggerFactory.getLogger(CacheConfiguration.class);
private net.sf.ehcache.CacheManager cacheManager;
@Inject
private Environment env;
@PreDestroy
public void destroy() {
log.info("Closing Ehcache");
cacheManager.shutdown();
}
@Bean
public CacheManager cacheManager() {
cacheManager = new net.sf.ehcache.CacheManager();
if (env.acceptsProfiles(Constants.SPRING_PROFILE_METRICS)) {
log.debug("Ehcache Metrics monitoring enabled");
Cache statusCache = cacheManager.getCache("status-cache");
Ehcache decoratedStatusCache = InstrumentedEhcache.instrument(statusCache);
cacheManager.replaceCacheWithDecoratedCache(statusCache, decoratedStatusCache);
Cache userCache = cacheManager.getCache("user-cache");
Ehcache decoratedUserCache = InstrumentedEhcache.instrument(userCache);
cacheManager.replaceCacheWithDecoratedCache(userCache, decoratedUserCache);
Cache attachmentCache = cacheManager.getCache("attachment-cache");
Ehcache decoratedAttachmentCache = InstrumentedEhcache.instrument(attachmentCache);
cacheManager.replaceCacheWithDecoratedCache(attachmentCache, decoratedAttachmentCache);
Cache friendsCache = cacheManager.getCache("friends-cache");
Ehcache decoratedFriendsCache = InstrumentedEhcache.instrument(friendsCache);
cacheManager.replaceCacheWithDecoratedCache(friendsCache, decoratedFriendsCache);
Cache followersCache = cacheManager.getCache("followers-cache");
Ehcache decoratedFollowersCache = InstrumentedEhcache.instrument(followersCache);
cacheManager.replaceCacheWithDecoratedCache(followersCache, decoratedFollowersCache);
Cache groupCache = cacheManager.getCache("group-cache");
Ehcache decoratedGroupCache = InstrumentedEhcache.instrument(groupCache);
cacheManager.replaceCacheWithDecoratedCache(groupCache, decoratedGroupCache);
Cache groupUserCache = cacheManager.getCache("group-user-cache");
Ehcache decoratedGroupUserCache = InstrumentedEhcache.instrument(groupUserCache);
cacheManager.replaceCacheWithDecoratedCache(groupUserCache, decoratedGroupUserCache);
}
EhCacheCacheManager ehCacheManager = new EhCacheCacheManager();
ehCacheManager.setCacheManager(cacheManager);
return ehCacheManager;
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/config/CassandraConfiguration.java
================================================
package fr.ippon.tatami.config;
import me.prettyprint.cassandra.connection.HOpTimer;
import me.prettyprint.cassandra.connection.MetricsOpTimer;
import me.prettyprint.cassandra.model.ConfigurableConsistencyLevel;
import me.prettyprint.cassandra.service.CassandraHostConfigurator;
import me.prettyprint.cassandra.service.ThriftCfDef;
import me.prettyprint.cassandra.service.ThriftCluster;
import me.prettyprint.cassandra.service.ThriftKsDef;
import me.prettyprint.hector.api.Cluster;
import me.prettyprint.hector.api.HConsistencyLevel;
import me.prettyprint.hector.api.Keyspace;
import me.prettyprint.hector.api.ddl.ColumnFamilyDefinition;
import me.prettyprint.hector.api.ddl.ComparatorType;
import me.prettyprint.hector.api.ddl.KeyspaceDefinition;
import me.prettyprint.hector.api.factory.HFactory;
import me.prettyprint.hom.EntityManagerImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import javax.annotation.PreDestroy;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.List;
/**
* Cassandra configuration file.
*
* @author Julien Dubois
*/
@Configuration
public class CassandraConfiguration {
private final Logger log = LoggerFactory.getLogger(CassandraConfiguration.class);
@Inject
private Environment env;
private Cluster myCluster;
@PreDestroy
public void destroy() {
log.info("Closing Hector connection pool");
myCluster.getConnectionManager().shutdown();
HFactory.shutdownCluster(myCluster);
}
@Bean
public Keyspace keyspaceOperator() {
log.info("Configuring Cassandra keyspace");
String cassandraHost = env.getProperty("cassandra.host");
String cassandraClusterName = env.getProperty("cassandra.clusterName");
String cassandraKeyspace = env.getProperty("cassandra.keyspace");
CassandraHostConfigurator cassandraHostConfigurator = new CassandraHostConfigurator(cassandraHost);
cassandraHostConfigurator.setMaxActive(100);
if (env.acceptsProfiles(Constants.SPRING_PROFILE_METRICS)) {
log.debug("Cassandra Metrics monitoring enabled");
HOpTimer hOpTimer = new MetricsOpTimer(cassandraClusterName);
cassandraHostConfigurator.setOpTimer(hOpTimer);
}
ThriftCluster cluster = new ThriftCluster(cassandraClusterName, cassandraHostConfigurator);
this.myCluster = cluster; // Keep a pointer to the cluster, as Hector is buggy and can't find it again...
ConfigurableConsistencyLevel consistencyLevelPolicy = new ConfigurableConsistencyLevel();
consistencyLevelPolicy.setDefaultReadConsistencyLevel(HConsistencyLevel.ONE);
KeyspaceDefinition keyspaceDef = cluster.describeKeyspace(cassandraKeyspace);
if (keyspaceDef == null) {
log.warn("Keyspace \" {} \" does not exist, creating it!", cassandraKeyspace);
keyspaceDef = new ThriftKsDef(cassandraKeyspace);
cluster.addKeyspace(keyspaceDef, true);
addColumnFamily(cluster, ColumnFamilyKeys.USER_CF, 0);
addColumnFamily(cluster, ColumnFamilyKeys.FRIENDS_CF, 0);
addColumnFamily(cluster, ColumnFamilyKeys.FOLLOWERS_CF, 0);
addColumnFamily(cluster, ColumnFamilyKeys.STATUS_CF, 0);
addColumnFamily(cluster, ColumnFamilyKeys.DOMAIN_CF, 0);
addColumnFamily(cluster, ColumnFamilyKeys.REGISTRATION_CF, 0);
addColumnFamily(cluster, ColumnFamilyKeys.RSS_CF, 0);
addColumnFamily(cluster, ColumnFamilyKeys.MAILDIGEST_CF, 0);
addColumnFamily(cluster, ColumnFamilyKeys.SHARES_CF, 0);
addColumnFamily(cluster, ColumnFamilyKeys.DISCUSSION_CF, 0);
addColumnFamily(cluster, ColumnFamilyKeys.USER_TAGS_CF, 0);
addColumnFamily(cluster, ColumnFamilyKeys.TAG_FOLLOWERS_CF, 0);
addColumnFamily(cluster, ColumnFamilyKeys.GROUP_MEMBERS_CF, 0);
addColumnFamily(cluster, ColumnFamilyKeys.USER_GROUPS_CF, 0);
addColumnFamily(cluster, ColumnFamilyKeys.GROUP_CF, 0);
addColumnFamily(cluster, ColumnFamilyKeys.GROUP_DETAILS_CF, 0);
addColumnFamily(cluster, ColumnFamilyKeys.ATTACHMENT_CF, 0);
addColumnFamily(cluster, ColumnFamilyKeys.AVATAR_CF, 0);
addColumnFamily(cluster, ColumnFamilyKeys.DOMAIN_CONFIGURATION_CF, 0);
addColumnFamily(cluster, ColumnFamilyKeys.TATAMIBOT_CONFIGURATION_CF, 0);
addColumnFamily(cluster, ColumnFamilyKeys.APPLE_DEVICE_CF, 0);
addColumnFamily(cluster, ColumnFamilyKeys.BLOCK_USERS_CF, 0);
addColumnFamily(cluster, ColumnFamilyKeys.STATUS_REPORT_CF, 0);
addColumnFamilySortedbyUUID(cluster, ColumnFamilyKeys.TIMELINE_CF, 0);
addColumnFamilySortedbyUUID(cluster, ColumnFamilyKeys.TIMELINE_SHARES_CF, 0);
addColumnFamilySortedbyUUID(cluster, ColumnFamilyKeys.MENTIONLINE_CF, 0);
addColumnFamilySortedbyUUID(cluster, ColumnFamilyKeys.USERLINE_CF, 0);
addColumnFamilySortedbyUUID(cluster, ColumnFamilyKeys.USERLINE_SHARES_CF, 0);
addColumnFamilySortedbyUUID(cluster, ColumnFamilyKeys.FAVLINE_CF, 0);
addColumnFamilySortedbyUUID(cluster, ColumnFamilyKeys.TAGLINE_CF, 0);
addColumnFamilySortedbyUUID(cluster, ColumnFamilyKeys.TRENDS_CF, 0);
addColumnFamilySortedbyUUID(cluster, ColumnFamilyKeys.USER_TRENDS_CF, 0);
addColumnFamilySortedbyUUID(cluster, ColumnFamilyKeys.GROUPLINE_CF, 0);
addColumnFamilySortedbyUUID(cluster, ColumnFamilyKeys.USER_ATTACHMENT_CF, 0);
addColumnFamilySortedbyUUID(cluster, ColumnFamilyKeys.STATUS_ATTACHMENT_CF, 0);
addColumnFamilySortedbyUUID(cluster, ColumnFamilyKeys.DOMAINLINE_CF, 0);
addColumnFamilySortedbyUUID(cluster, ColumnFamilyKeys.DOMAIN_TATAMIBOT_CF, 0);
addColumnFamilyCounter(cluster, ColumnFamilyKeys.COUNTER_CF, 0);
addColumnFamilyCounter(cluster, ColumnFamilyKeys.TAG_COUNTER_CF, 0);
addColumnFamilyCounter(cluster, ColumnFamilyKeys.GROUP_COUNTER_CF, 0);
addColumnFamilyCounter(cluster, ColumnFamilyKeys.DAYLINE_CF, 0);
//Tatami Bot CF
addColumnFamily(cluster, ColumnFamilyKeys.TATAMIBOT_DUPLICATE_CF, 0);
} else {
/*
This case only concerns the creation of new CF that didn't exist in the previous version of Tatami.
As we cannot afford to drop the keyspace in production, this case is an update of the database that
should be executed only one time in production.
*/
List lcf = keyspaceDef.getCfDefs();
List lcfNames = new ArrayList();
for(ColumnFamilyDefinition cfd : lcf){
lcfNames.add(cfd.getName());
}
// The new tables
if(!lcfNames.contains(ColumnFamilyKeys.BLOCK_USERS_CF)){
addColumnFamily(cluster, ColumnFamilyKeys.BLOCK_USERS_CF, 0);
log.debug("{} column family successfully created", ColumnFamilyKeys.BLOCK_USERS_CF);
}
if(!lcfNames.contains(ColumnFamilyKeys.STATUS_REPORT_CF)){
addColumnFamily(cluster, ColumnFamilyKeys.STATUS_REPORT_CF, 0);
log.debug("{} column family successfully created", ColumnFamilyKeys.STATUS_REPORT_CF);
}
}
return HFactory.createKeyspace(cassandraKeyspace, cluster, consistencyLevelPolicy);
}
@Bean
public EntityManagerImpl entityManager(Keyspace keyspace) {
String[] packagesToScan = {"fr.ippon.tatami.domain", "fr.ippon.tatami.bot.config"};
return new EntityManagerImpl(keyspace, packagesToScan);
}
private void addColumnFamily(ThriftCluster cluster, String cfName, int rowCacheKeysToSave) {
String cassandraKeyspace = this.env.getProperty("cassandra.keyspace");
ColumnFamilyDefinition cfd =
HFactory.createColumnFamilyDefinition(cassandraKeyspace, cfName);
cfd.setRowCacheKeysToSave(rowCacheKeysToSave);
cluster.addColumnFamily(cfd);
}
private void addColumnFamilySortedbyUUID(ThriftCluster cluster, String cfName, int rowCacheKeysToSave) {
String cassandraKeyspace = this.env.getProperty("cassandra.keyspace");
ColumnFamilyDefinition cfd =
HFactory.createColumnFamilyDefinition(cassandraKeyspace, cfName);
cfd.setRowCacheKeysToSave(rowCacheKeysToSave);
cfd.setComparatorType(ComparatorType.UUIDTYPE);
cluster.addColumnFamily(cfd);
}
private void addColumnFamilyCounter(ThriftCluster cluster, String cfName, int rowCacheKeysToSave) {
String cassandraKeyspace = this.env.getProperty("cassandra.keyspace");
ThriftCfDef cfd =
new ThriftCfDef(cassandraKeyspace, cfName, ComparatorType.UTF8TYPE);
cfd.setRowCacheKeysToSave(rowCacheKeysToSave);
cfd.setDefaultValidationClass(ComparatorType.COUNTERTYPE.getClassName());
cluster.addColumnFamily(cfd);
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/config/ColumnFamilyKeys.java
================================================
package fr.ippon.tatami.config;
/**
* @author Julien Dubois
*/
public class ColumnFamilyKeys {
private ColumnFamilyKeys() {
}
public static final String USER_CF = "User";
public static final String FRIENDS_CF = "Friends";
public static final String FOLLOWERS_CF = "Followers";
public static final String STATUS_CF = "Status";
public static final String SHARES_CF = "Shares";
public static final String DISCUSSION_CF = "Discussion";
public static final String DAYLINE_CF = "Dayline";
public static final String FAVLINE_CF = "Favline";
public static final String TAGLINE_CF = "Tagline";
public static final String TIMELINE_CF = "Timeline";
public static final String TIMELINE_SHARES_CF = "TimelineShares";
public static final String MENTIONLINE_CF = "Mentionline";
public static final String USERLINE_CF = "Userline";
public static final String USERLINE_SHARES_CF = "UserlineShares";
public static final String COUNTER_CF = "Counter";
public static final String DOMAIN_CF = "Domain";
public static final String REGISTRATION_CF = "Registration";
public static final String RSS_CF = "Rss";
public static final String MAILDIGEST_CF = "MailDigest";
public static final String TRENDS_CF = "Trends";
public static final String TAG_FOLLOWERS_CF = "TagFollowers";
public static final String USER_TAGS_CF = "UserTags";
public static final String TAG_COUNTER_CF = "TagCounter";
public static final String USER_TRENDS_CF = "UserTrends";
public static final String GROUP_CF = "Group";
public static final String GROUP_DETAILS_CF = "GroupDetails";
public static final String GROUP_MEMBERS_CF = "GroupMembers";
public static final String USER_GROUPS_CF = "UserGroups";
public static final String GROUP_COUNTER_CF = "GroupCounter";
public static final String GROUPLINE_CF = "Groupline";
public static final String TATAMIBOT_DUPLICATE_CF = "TatamiBotDuplicate";
public static final String ATTACHMENT_CF = "Attachment";
public static final String USER_ATTACHMENT_CF = "UserAttachments";
public static final String STATUS_ATTACHMENT_CF = "StatusAttachments";
public static final String DOMAIN_CONFIGURATION_CF = "DomainConfiguration";
public static final String DOMAINLINE_CF = "Domainline";
public static final String DOMAIN_TATAMIBOT_CF = "DomainTatamibot";
public static final String TATAMIBOT_CONFIGURATION_CF = "TatamibotConfiguration";
public static final String AVATAR_CF = "Avatar";
public static final String APPLE_DEVICE_CF = "AppleDevice";
public static final String APPLE_DEVICE_USER_CF = "AppleDeviceUser";
public static final String BLOCK_USERS_CF = "UsersBlocked";
public static final String STATUS_REPORT_CF = "ReportedStatus";
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/config/Constants.java
================================================
package fr.ippon.tatami.config;
/**
* Application constants.
*/
public class Constants {
private Constants() {
}
public static final String SPRING_PROFILE_METRICS = "metrics";
public static final String REMOTE_ENGINE = "remote";
public static final String EMBEDDED_ENGINE = "embedded";
public static String VERSION = null;
public static String GOOGLE_ANALYTICS_KEY = null;
public static final int PAGINATION_SIZE = 50;
public static final String TATAMIBOT_NAME = "tatamibot";
public static final int AVATAR_SIZE = 200;
/**
* Cassandra : number of columns to return when not doing a name-based template
*/
public static final int CASSANDRA_MAX_COLUMNS = 10000;
/**
* Cassandra : number of rows to return
*/
public static final int CASSANDRA_MAX_ROWS = 10000;
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/config/DispatcherServletConfig.java
================================================
package fr.ippon.tatami.config;
import fr.ippon.tatami.web.syndic.SyndicView;
import org.apache.commons.lang.CharEncoding;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.*;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.core.env.Environment;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.view.ContentNegotiatingViewResolver;
import org.springframework.web.servlet.view.JstlView;
import org.springframework.web.servlet.view.UrlBasedViewResolver;
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;
@Configuration
@ComponentScan("fr.ippon.tatami.web")
@EnableWebMvc
@PropertySource({"classpath:/META-INF/tatami/tatami.properties",
"classpath:/META-INF/tatami/customization.properties"})
@ImportResource("classpath:META-INF/spring/applicationContext-metrics.xml")
public class DispatcherServletConfig extends WebMvcConfigurerAdapter {
private final Logger log = LoggerFactory.getLogger(DispatcherServletConfig.class);
@Inject
private Environment env;
@Bean
public ViewResolver ContentNegotiatingViewResolver() {
log.debug("Configuring the ContentNegotiatingViewResolver");
ContentNegotiatingViewResolver viewResolver = new ContentNegotiatingViewResolver();
List viewResolvers = new ArrayList();
UrlBasedViewResolver urlBasedViewResolver = new UrlBasedViewResolver();
urlBasedViewResolver.setViewClass(JstlView.class);
urlBasedViewResolver.setPrefix("/WEB-INF/pages/");
urlBasedViewResolver.setSuffix(".jsp");
viewResolvers.add(urlBasedViewResolver);
viewResolver.setViewResolvers(viewResolvers);
List defaultViews = new ArrayList();
defaultViews.add(new MappingJackson2JsonView());
defaultViews.add(syndicView());
viewResolver.setDefaultViews(defaultViews);
return viewResolver;
}
@Bean
public SyndicView syndicView() {
return new SyndicView();
}
@Bean
public SessionLocaleResolver localeResolver() {
return new SessionLocaleResolver();
}
@Bean
public LocaleChangeInterceptor localeChangeInterceptor() {
log.debug("Configuring localeChangeInterceptor");
LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
localeChangeInterceptor.setParamName("language");
return localeChangeInterceptor;
}
@Bean
public MessageSource messageSource() {
log.debug("Loading MessageSources");
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("/WEB-INF/messages/messages");
messageSource.setDefaultEncoding(CharEncoding.UTF_8);
if ("true".equals(env.getProperty("tatami.message.reloading.enabled"))) {
messageSource.setCacheSeconds(1);
}
return messageSource;
}
@Bean
public MultipartResolver multipartResolver() {
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
Long maxSize = Long.parseLong(env.getProperty("file.max.size"));
multipartResolver.setMaxUploadSize(maxSize); // 10 Mo max file size by default
return multipartResolver;
}
@Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
log.debug("Creating requestMappingHandlerMapping");
RequestMappingHandlerMapping requestMappingHandlerMapping = new RequestMappingHandlerMapping();
requestMappingHandlerMapping.setUseSuffixPatternMatch(false);
Object[] interceptors = {localeChangeInterceptor()};
requestMappingHandlerMapping.setInterceptors(interceptors);
return requestMappingHandlerMapping;
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
log.debug("Adding static resource handlers");
registry.addResourceHandler("/static-wro4j/" + env.getProperty("tatami.version") + "/**")
.addResourceLocations("/WEB-INF/generated-wro4j/")
.setCachePeriod(60 * 60 * 24 * 30);
registry.addResourceHandler("/css/**")
.addResourceLocations("/css/")
.setCachePeriod(60 * 60 * 24 * 30);
registry.addResourceHandler("/static/" + env.getProperty("tatami.version") + "/**")
.addResourceLocations("/js/")
.setCachePeriod(60 * 60 * 24 * 30);
}
@Override
public void configureHandlerExceptionResolvers(List exceptionResolvers) {
exceptionResolvers.add(new HandlerExceptionResolver() {
@Override
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
try {
if (log.isErrorEnabled()) {
log.error("An error has occured : " + ex.getMessage());
ex.printStackTrace();
}
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
return new ModelAndView();
} catch (Exception handlerException) {
log.warn("Handling of [" + ex.getClass().getName() + "] resulted in Exception", handlerException);
}
return null;
}
});
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/config/GroupRoles.java
================================================
package fr.ippon.tatami.config;
/**
* Constants for groupe roles.
*/
public class GroupRoles {
private GroupRoles() {
}
public static final String ADMIN = "ADMIN";
public static final String MEMBER = "MEMBER";
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/config/MailConfiguration.java
================================================
package fr.ippon.tatami.config;
import org.apache.commons.lang.CharEncoding;
import org.apache.velocity.app.VelocityEngine;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.ui.velocity.VelocityEngineFactoryBean;
import java.io.IOException;
import java.util.Properties;
/**
* Configuration for velocity template and i18n for emails.
*
* @author Pierre Rust
*/
@Configuration
public class MailConfiguration {
private static final Logger log = LoggerFactory.getLogger(MailConfiguration.class);
@Bean
public VelocityEngine velocityEngine() throws IOException {
log.debug("Starting Velocity Engine");
VelocityEngineFactoryBean factory = new VelocityEngineFactoryBean();
Properties props = new Properties();
props.put("resource.loader", "class");
props.put("class.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
// necessary to get logs on templates's error
props.put("runtime.log.logsystem.class", "org.apache.velocity.runtime.log.Log4JLogChute");
props.put("runtime.log.error.stacktrace", "true");
props.put("runtime.log.warn.stacktrace", "true");
props.put("runtime.log.info.stacktrace", "true");
props.put("runtime.log.invalid.reference", "true");
// TODO : FileResourceLoader could be used to externalize templates
// enable relative includes
props.put("eventhandler.include.class", "org.apache.velocity.app.event.implement.IncludeRelativePath");
factory.setVelocityProperties(props);
return factory.createVelocityEngine();
}
@Bean
public MessageSource mailMessageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:/META-INF/tatami/mails/messages/messages");
messageSource.setDefaultEncoding(CharEncoding.UTF_8);
log.info("loading non-reloadable mail messages resources");
return messageSource;
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/config/MetricsConfiguration.java
================================================
package fr.ippon.tatami.config;
import com.yammer.metrics.HealthChecks;
import com.yammer.metrics.reporting.GraphiteReporter;
import fr.ippon.tatami.config.metrics.CassandraHealthCheck;
import fr.ippon.tatami.config.metrics.JavaMailHealthCheck;
import fr.ippon.tatami.service.MailService;
import me.prettyprint.hector.api.Keyspace;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import java.util.concurrent.TimeUnit;
@Configuration
public class MetricsConfiguration {
private final Logger log = LoggerFactory.getLogger(MetricsConfiguration.class);
@Inject
private Environment env;
@Inject
private Keyspace keyspaceOperator;
@Inject
private MailService mailService;
@PostConstruct
public void initMetrics() {
if (env.acceptsProfiles(Constants.SPRING_PROFILE_METRICS)) {
log.debug("Initializing Metrics healthchecks");
HealthChecks.register(new CassandraHealthCheck(keyspaceOperator));
HealthChecks.register(new JavaMailHealthCheck(mailService));
String graphiteHost = env.getProperty("tatami.metrics.graphite.host");
if (graphiteHost != null) {
log.debug("Initializing Metrics Graphite reporting");
Integer graphitePort = env.getProperty("tatami.metrics.graphite.port", Integer.class);
GraphiteReporter.enable(1,
TimeUnit.MINUTES,
graphiteHost,
graphitePort);
} else {
log.warn("Graphite server is not configured, unable to send any data to Graphite");
}
}
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/config/SearchConfiguration.java
================================================
package fr.ippon.tatami.config;
import fr.ippon.tatami.service.elasticsearch.ElasticsearchEngine;
import fr.ippon.tatami.service.elasticsearch.EmbeddedElasticsearchEngine;
import fr.ippon.tatami.service.elasticsearch.RemoteElasticsearchEngine;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import javax.inject.Inject;
/**
* Search configuration, with Elastic Search.
*/
@Configuration
public class SearchConfiguration {
private final Logger log = LoggerFactory.getLogger(SearchConfiguration.class);
@Inject
private Environment env;
@Bean
public ElasticsearchEngine elasticsearchEngine() {
log.info("Starting Elasticsearch");
String mode = env.getRequiredProperty("elasticsearch.engine.mode");
if (Constants.REMOTE_ENGINE.equalsIgnoreCase(mode)) {
return new RemoteElasticsearchEngine();
} else if (Constants.EMBEDDED_ENGINE.equalsIgnoreCase(mode)) {
return new EmbeddedElasticsearchEngine();
} else {
//Log do not support log.fatal
log.error("Elasticsearch engine mode is not defined, please configure the \"elasticsearch.engine.mode\" property");
throw new IllegalArgumentException("Elasticsearch engine mode " + mode + " not defined");
}
}
@Bean
public String indexNamePrefix() {
return env.getProperty("elasticsearch.indexNamePrefix");
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/config/metrics/CassandraHealthCheck.java
================================================
package fr.ippon.tatami.config.metrics;
import com.yammer.metrics.core.HealthCheck;
import me.prettyprint.cassandra.serializers.StringSerializer;
import me.prettyprint.hector.api.Keyspace;
import me.prettyprint.hector.api.exceptions.HectorException;
import static fr.ippon.tatami.config.ColumnFamilyKeys.DOMAIN_CF;
import static me.prettyprint.hector.api.factory.HFactory.createRangeSlicesQuery;
/**
* Metrics HealthCheck for Cassandra.
*/
public class CassandraHealthCheck extends HealthCheck {
private final Keyspace keyspaceOperator;
public CassandraHealthCheck(Keyspace keyspaceOperator) {
super("Cassandra");
this.keyspaceOperator = keyspaceOperator;
}
@Override
public Result check() throws Exception {
try {
createRangeSlicesQuery(keyspaceOperator,
StringSerializer.get(), StringSerializer.get(), StringSerializer.get())
.setColumnFamily(DOMAIN_CF)
.setRange(null, null, false, 1)
.execute()
.get();
return Result.healthy();
} catch (HectorException he) {
return Result.unhealthy("Cannot connect to Cassandra Cluster : " + keyspaceOperator.getKeyspaceName());
}
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/config/metrics/JavaMailHealthCheck.java
================================================
package fr.ippon.tatami.config.metrics;
import com.yammer.metrics.core.HealthCheck;
import fr.ippon.tatami.service.MailService;
/**
* Metrics HealthCheck for JavaMail.
*/
public class JavaMailHealthCheck extends HealthCheck {
private final MailService mailService;
public JavaMailHealthCheck(MailService mailService) {
super("JavaMail");
this.mailService = mailService;
}
@Override
public Result check() throws Exception {
if (mailService.connectSmtpServer()) {
return Result.healthy();
} else {
return Result.unhealthy("Cannot connect to Mail server");
}
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/domain/Attachment.java
================================================
package fr.ippon.tatami.domain;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.DateTimeFormatterBuilder;
import java.io.Serializable;
import java.util.Date;
public class Attachment implements Serializable {
private static final DateTimeFormatter oldDateFormatter = new DateTimeFormatterBuilder()
.appendDayOfMonth(1)
.appendLiteral(' ')
.appendMonthOfYearShortText()
.appendLiteral(' ')
.appendYear(4, 4)
.toFormatter();
private String attachmentId;
private String filename;
@JsonIgnore
private Date creationDate;
private String prettyPrintCreationDate;
@JsonIgnore
private byte[] content;
@JsonIgnore
private byte[] thumbnail;
private boolean hasThumbnail = false;
private long size;
public String getAttachmentId() {
return attachmentId;
}
public void setAttachmentId(String attachmentId) {
this.attachmentId = attachmentId;
}
public String getFilename() {
return filename;
}
public void setFilename(String filename) {
this.filename = filename;
}
public Date getCreationDate() {
return creationDate;
}
public void setCreationDate(Date creationDate) {
this.creationDate = creationDate;
DateTime dateTime = new DateTime(creationDate);
this.prettyPrintCreationDate = oldDateFormatter.print(dateTime);
}
public String getPrettyPrintCreationDate() {
return prettyPrintCreationDate;
}
public void setPrettyPrintCreationDate(String prettyPrintCreationDate) {
this.prettyPrintCreationDate = prettyPrintCreationDate;
}
public byte[] getContent() {
return content;
}
public void setContent(byte[] content) {
this.content = content;
}
public byte[] getThumbnail() {
return thumbnail;
}
public void setThumbnail(byte[] thumbnail) {
this.thumbnail = thumbnail;
}
public boolean getHasThumbnail() {
return this.hasThumbnail;
}
public void setHasThumbnail(boolean hasThumbnail) {
this.hasThumbnail = hasThumbnail;
}
public long getSize() {
return size;
}
public void setSize(long size) {
this.size = size;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Attachment that = (Attachment) o;
return attachmentId.equals(that.attachmentId);
}
@Override
public int hashCode() {
return attachmentId.hashCode();
}
@Override
public String toString() {
return "Attachment{" +
"attachmentId='" + attachmentId + '\'' +
", filename='" + filename + '\'' +
", prettyPrintCreationDate='" + prettyPrintCreationDate + '\'' +
", size=" + size +
'}';
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/domain/Avatar.java
================================================
package fr.ippon.tatami.domain;
import com.fasterxml.jackson.annotation.JsonIgnore;
import java.io.Serializable;
import java.util.Date;
public class Avatar implements Serializable {
private String avatarId;
private String filename;
@JsonIgnore
private Date creationDate;
@JsonIgnore
private byte[] content;
private long size;
public String getAvatarId() {
return avatarId;
}
public void setAvatarId(String AvatarId) {
this.avatarId = AvatarId;
}
public String getFilename() {
return filename;
}
public void setFilename(String filename) {
this.filename = filename;
}
public Date getCreationDate() {
return creationDate;
}
public void setCreationDate(Date creationDate) {
this.creationDate = creationDate;
}
public byte[] getContent() {
return content;
}
public void setContent(byte[] content) {
this.content = content;
}
public long getSize() {
return size;
}
public void setSize(long size) {
this.size = size;
}
@Override
public int hashCode() {
return avatarId.hashCode();
}
@Override
public String toString() {
return "Avatar{" +
"avatarId='" + avatarId + '\'' +
", filename='" + filename + '\'' +
", CreationDate='" + creationDate + '\'' +
", size=" + size +
'}';
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/domain/DigestType.java
================================================
package fr.ippon.tatami.domain;
/**
* @author Pierre Rust
*/
public enum DigestType {
WEEKLY_DIGEST("WEEKLY"),
DAILY_DIGEST("DAILY");
private DigestType(final String text) {
this.text = text;
}
private final String text;
/* (non-Javadoc)
* @see java.lang.Enum#toString()
*/
@Override
public String toString() {
return text;
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/domain/Domain.java
================================================
package fr.ippon.tatami.domain;
import java.io.Serializable;
/**
* A domain is a domain name (e.g. "ippon.fr"), and represents a company.
*/
public class Domain implements Serializable, Comparable {
private String name;
private int numberOfUsers;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getNumberOfUsers() {
return numberOfUsers;
}
public void setNumberOfUsers(int numberOfUsers) {
this.numberOfUsers = numberOfUsers;
}
@Override
public int compareTo(Domain other) {
return this.name.compareTo(other.name);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
} else if (o == null || getClass() != o.getClass()) {
return false;
}
Domain domain = (Domain) o;
return name.equals(domain.name);
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public String toString() {
return "Domain{" +
"name='" + name + '\'' +
", numberOfUsers=" + numberOfUsers +
'}';
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/domain/DomainConfiguration.java
================================================
package fr.ippon.tatami.domain;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.Properties;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import org.apache.commons.io.IOUtils;
/**
* The configuration for a specific domain.
*
* @author Julien Dubois
*/
@Entity
@Table(name = "DomainConfiguration")
public class DomainConfiguration implements Serializable {
public static class SubscriptionAndStorageSizeOptions {
public static String BASICSIZE = "10";
public static String PREMIUMSIZE = "1000";
public static String IPPONSIZE = "100000";
public static String BASICSUSCRIPTION = "0";
public static String PREMIUMSUSCRIPTION = "1";
public static String IPPONSUSCRIPTION = "-1";
static{
InputStream inputStream=null;
try{
inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("META-INF/tatami/tatami.properties");
Properties props = new Properties();
props.load(inputStream);
String basicSize = "storage.basic.max.size";
String premiumSize = "storage.premium.max.size";
String ipponSize = "storage.ippon.max.size";
String basicSuscription = "suscription.level.free";
String premiumSuscription = "suscription.level.premium";
String ipponSuscription = "suscription.level.ippon";
if(!props.containsKey(basicSize) || !props.containsKey(premiumSize)
|| !props.containsKey(ipponSize) || !props.containsKey(basicSuscription)
|| !props.containsKey(premiumSuscription) || !props.containsKey(ipponSuscription))
throw new IllegalStateException("Property not found");
BASICSIZE= props.getProperty(basicSize);
PREMIUMSIZE= props.getProperty(premiumSize);
IPPONSIZE= props.getProperty(ipponSize);
BASICSUSCRIPTION= props.getProperty(basicSuscription);
PREMIUMSUSCRIPTION= props.getProperty(premiumSuscription);
IPPONSUSCRIPTION= props.getProperty(ipponSuscription);
} catch(IOException e){
throw new IllegalStateException(e);
}finally{
// apache commons / IO
IOUtils.closeQuietly(inputStream);
}
}
}
@Id
private String domain;
@Column(name = "subscriptionLevel")
private String subscriptionLevel;
@Column(name = "storageSize")
private String storageSize;
@Column(name = "adminLogin")
private String adminLogin;
public String getDomain() {
return domain;
}
public void setDomain(String domain) {
this.domain = domain;
}
public String getSubscriptionLevel() {
return subscriptionLevel;
}
public void setSubscriptionLevel(String subscriptionLevel) {
this.subscriptionLevel = subscriptionLevel;
}
public String getStorageSize() {
return storageSize;
}
public long getStorageSizeAsLong() {
try {
return Long.parseLong(this.storageSize) * 1000000;
} catch (NumberFormatException nfe) {
return Long.parseLong(SubscriptionAndStorageSizeOptions.BASICSIZE) * 1000000;
}
}
public void setStorageSize(String storageSize) {
this.storageSize = storageSize;
}
public String getAdminLogin() {
return adminLogin;
}
public void setAdminLogin(String adminLogin) {
this.adminLogin = adminLogin;
}
@Override
public String toString() {
return "DomainConfiguration{" +
"domain='" + domain + '\'' +
", subscriptionLevel='" + subscriptionLevel + '\'' +
", storageSize='" + storageSize + '\'' +
", adminLogin='" + adminLogin + '\'' +
'}';
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/domain/Group.java
================================================
package fr.ippon.tatami.domain;
import com.fasterxml.jackson.annotation.JsonIgnore;
import java.io.Serializable;
/**
* A group.
*/
public class Group implements Comparable, Serializable, Cloneable {
private String groupId;
private boolean publicGroup;
private boolean archivedGroup;
private String name;
private String description;
@JsonIgnore
private String domain;
private long counter;
private boolean member;
private boolean administrator;
public String getGroupId() {
return groupId;
}
public void setGroupId(String groupId) {
this.groupId = groupId;
}
public boolean isPublicGroup() {
return publicGroup;
}
public void setPublicGroup(boolean publicGroup) {
this.publicGroup = publicGroup;
}
public boolean isArchivedGroup() {
return archivedGroup;
}
public void setArchivedGroup(boolean archivedGroup) {
this.archivedGroup = archivedGroup;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getDomain() {
return domain;
}
public void setDomain(String domain) {
this.domain = domain;
}
public long getCounter() {
return counter;
}
public void setCounter(long counter) {
this.counter = counter;
}
public boolean isMember() {
return member;
}
public void setMember(boolean member) {
this.member = member;
}
public boolean isAdministrator() {
return administrator;
}
public void setAdministrator(boolean administrator) {
this.administrator = administrator;
}
@Override
public int compareTo(Group o) {
if (this.getName() == null) {
return -1;
}
if (o.getName() == null) {
return 1;
}
if (this.getName().equals(o.getName())) {
return this.getGroupId().compareTo(o.getGroupId()); // To display duplicates
}
return this.getName().compareTo(o.getName());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Group group = (Group) o;
return groupId.equals(group.groupId);
}
@Override
public int hashCode() {
return groupId.hashCode();
}
@Override
public String toString() {
return "Group{" +
"groupId='" + groupId + '\'' +
", publicGroup=" + publicGroup +
", archivedGroup=" + archivedGroup +
", name='" + name + '\'' +
", domain='" + domain + '\'' +
", counter=" + counter +
", member=" + member +
", administrator=" + administrator +
'}';
}
@Override
public Object clone() {
Group clone = null;
try {
clone = (Group) super.clone();
} catch (CloneNotSupportedException cnse) {
cnse.printStackTrace(System.err);
}
return clone;
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/domain/User.java
================================================
package fr.ippon.tatami.domain;
import com.fasterxml.jackson.annotation.JsonIgnore;
import fr.ippon.tatami.domain.validation.ContraintsUserCreation;
import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.NotEmpty;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import javax.validation.groups.Default;
import java.io.Serializable;
/**
* A user.
*
* @author Julien Dubois
*/
@Entity
@Table(name = "User")
public class User implements Serializable {
@NotEmpty(message = "Login is mandatory.", groups = {ContraintsUserCreation.class, Default.class})
@NotNull(message = "Login is mandatory.", groups = {ContraintsUserCreation.class, Default.class})
@Email(message = "Email is invalid.")
@Id
@JsonIgnore
private String login;
@Column(name = "password")
@JsonIgnore
private String password;
@Column(name = "username")
private String username;
@Column(name = "domain")
@JsonIgnore
private String domain;
@Column(name = "avatar")
private String avatar;
@Size(min = 0, max = 50)
@Column(name = "firstName")
private String firstName;
@Size(min = 0, max = 50)
@Column(name = "lastName")
private String lastName;
@Size(min = 0, max = 100)
@Column(name = "jobTitle")
private String jobTitle;
@Size(min = 0, max = 20)
@Column(name = "phoneNumber")
private String phoneNumber;
@Column(name = "openIdUrl")
@JsonIgnore
private String openIdUrl;
@Column(name = "preferences_mention_email")
@JsonIgnore
private Boolean preferencesMentionEmail;
@Column(name = "rssUid")
@JsonIgnore
private String rssUid;
@Column(name = "weekly_digest_subscription")
@JsonIgnore
private Boolean weeklyDigestSubscription;
@Column(name = "daily_digest_subscription")
@JsonIgnore
private Boolean dailyDigestSubscription;
@Column(name = "attachmentsSize")
private long attachmentsSize;
@Column(name="activated")
private Boolean activated=true;
private long statusCount;
private long friendsCount;
private long followersCount;
private Boolean isAdmin = false;
public Boolean getActivated() {
return activated;
}
public void setActivated(Boolean activated) {
this.activated = activated;
}
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getDomain() {
return domain;
}
public void setDomain(String domain) {
this.domain = domain;
}
public String getAvatar() {
return avatar;
}
public void setAvatar(String avatar) {
this.avatar = avatar;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getJobTitle() {
return jobTitle;
}
public void setJobTitle(String jobTitle) {
this.jobTitle = jobTitle;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public String getOpenIdUrl() {
return openIdUrl;
}
public void setOpenIdUrl(String openIdUrl) {
this.openIdUrl = openIdUrl;
}
public Boolean getPreferencesMentionEmail() {
return preferencesMentionEmail;
}
public void setPreferencesMentionEmail(Boolean preferencesMentionEmail) {
this.preferencesMentionEmail = preferencesMentionEmail;
}
public long getAttachmentsSize() {
return attachmentsSize;
}
public void setAttachmentsSize(long attachmentsSize) {
this.attachmentsSize = attachmentsSize < 0 ? 0 : attachmentsSize;
}
public String getRssUid() {
return rssUid;
}
public void setRssUid(String rssUid) {
this.rssUid = rssUid;
}
public long getStatusCount() {
return statusCount;
}
public void setStatusCount(long statusCount) {
this.statusCount = statusCount;
}
public long getFriendsCount() {
return friendsCount;
}
public void setFriendsCount(long friendsCount) {
this.friendsCount = friendsCount;
}
public long getFollowersCount() {
return followersCount;
}
public void setFollowersCount(long followersCount) {
this.followersCount = followersCount;
}
public Boolean getIsAdmin() { return isAdmin; }
public void setIsAdmin(boolean isAdmin) { this.isAdmin = isAdmin; }
public Boolean getWeeklyDigestSubscription() {
return weeklyDigestSubscription;
}
public void setWeeklyDigestSubscription(Boolean weeklyDigestSubscription) {
this.weeklyDigestSubscription = weeklyDigestSubscription;
}
public Boolean getDailyDigestSubscription() {
return dailyDigestSubscription;
}
public void setDailyDigestSubscription(Boolean dailyDigestSubscription) {
this.dailyDigestSubscription = dailyDigestSubscription;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return login.equals(user.login);
}
@Override
public int hashCode() {
return login.hashCode();
}
@Override
public String toString() {
return "User{" +
"login='" + login + '\'' +
", password='" + password + '\'' +
", username='" + username + '\'' +
", domain='" + domain + '\'' +
", avatar='" + avatar + '\'' +
", firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
", jobTitle='" + jobTitle + '\'' +
", phoneNumber='" + phoneNumber + '\'' +
", openIdUrl='" + openIdUrl + '\'' +
", preferencesMentionEmail=" + preferencesMentionEmail +
", rssUid=" + rssUid +
", dailyDigestSubscription=" + dailyDigestSubscription +
", weeklyDigestSubscription=" + weeklyDigestSubscription +
", attachmentsSize=" + attachmentsSize +
", activated=" + activated +
", statusCount=" + statusCount +
", friendsCount=" + friendsCount +
", followersCount=" + followersCount +
'}';
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/domain/UserStatusStat.java
================================================
package fr.ippon.tatami.domain;
import java.io.Serializable;
public class UserStatusStat implements Comparable, Serializable {
private String username;
private Long statusCount;
public UserStatusStat(String username, Long count) {
assert username != null && count != null;
this.username = username;
this.statusCount = count;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Long getStatusCount() {
return statusCount;
}
public void setStatusCount(Long count) {
this.statusCount = count;
}
@Override
public int compareTo(UserStatusStat o) {
return this.username.compareTo(o.username);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
UserStatusStat that = (UserStatusStat) o;
return username.equals(that.username);
}
@Override
public int hashCode() {
return username.hashCode();
}
@Override
public String toString() {
return "UserStatusStat{" +
"username='" + username + '\'' +
", statusCount=" + statusCount +
'}';
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/domain/status/AbstractStatus.java
================================================
package fr.ippon.tatami.domain.status;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Date;
/**
* Parent class for all statuses.
*/
public abstract class AbstractStatus implements Serializable {
private String statusId;
@NotNull
private StatusType type;
@NotNull
private String login;
@NotNull
private String username;
@NotNull
private String domain;
private Date statusDate;
public String getGeoLocalization() {
return geoLocalization;
}
public void setGeoLocalization(String geoLocalization) {
this.geoLocalization = geoLocalization;
}
private String geoLocalization;
private boolean removed;
public String getStatusId() {
return statusId;
}
public void setStatusId(String statusId) {
this.statusId = statusId;
}
public StatusType getType() {
return type;
}
public void setType(StatusType type) {
this.type = type;
}
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getDomain() {
return domain;
}
public void setDomain(String domain) {
this.domain = domain;
}
public Date getStatusDate() {
return statusDate;
}
public void setStatusDate(Date statusDate) {
this.statusDate = statusDate;
}
public boolean isRemoved() {
return removed;
}
public void setRemoved(boolean removed) {
this.removed = removed;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AbstractStatus that = (AbstractStatus) o;
if (statusId != null ? !statusId.equals(that.statusId) : that.statusId != null) return false;
return true;
}
@Override
public int hashCode() {
return statusId != null ? statusId.hashCode() : 0;
}
@Override
public String toString() {
return "AbstractStatus{" +
"statusId='" + statusId + '\'' +
", type=" + type +
", login='" + login + '\'' +
", username='" + username + '\'' +
", domain='" + domain + '\'' +
", statusDate=" + statusDate +
", geoLocalization='" + geoLocalization + '\'' +
", removed=" + removed +
'}';
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/domain/status/Announcement.java
================================================
package fr.ippon.tatami.domain.status;
/**
* An announcement.
*/
public class Announcement extends Share {
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/domain/status/MentionFriend.java
================================================
package fr.ippon.tatami.domain.status;
/**
* Mention a user that someone started following him.
*/
public class MentionFriend extends AbstractStatus {
private String followerLogin;
public String getFollowerLogin() {
return followerLogin;
}
public void setFollowerLogin(String followerLogin) {
this.followerLogin = followerLogin;
}
@Override
public String toString() {
return "MentionFriend{" +
"followerLogin='" + followerLogin + '\'' +
"} " + super.toString();
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/domain/status/MentionShare.java
================================================
package fr.ippon.tatami.domain.status;
/**
* Mention a user that one of his statuses was shared.
*/
public class MentionShare extends Share {
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/domain/status/Share.java
================================================
package fr.ippon.tatami.domain.status;
/**
* A status that is shared.
*/
public class Share extends AbstractStatus {
private String originalStatusId;
public String getOriginalStatusId() {
return originalStatusId;
}
public void setOriginalStatusId(String originalStatusId) {
this.originalStatusId = originalStatusId;
}
@Override
public String toString() {
return "Share{" +
"originalStatusId='" + originalStatusId + '\'' +
"} " + super.toString();
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/domain/status/Status.java
================================================
package fr.ippon.tatami.domain.status;
import fr.ippon.tatami.domain.Attachment;
import org.hibernate.validator.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.Collection;
/**
* A status.
*
* @author Julien Dubois
*/
public class Status extends AbstractStatus {
private String groupId;
private Boolean statusPrivate;
private Boolean hasAttachments;
private Collection attachments;
@NotNull
@NotEmpty(message = "Content field is mandatory.")
@Size(min = 1, max = 2048)
private String content;
/**
* If this status is a reply, the statusId of the original status.
*/
private String discussionId;
/**
* If this status is a reply, the statusId of the status that is being replied to.
*/
private String replyTo;
/**
* If this status is a reply, the username of the status that is being replied to.
*/
private String replyToUsername;
private boolean detailsAvailable;
private Boolean removed;
public String getGroupId() {
return groupId;
}
public void setGroupId(String groupId) {
this.groupId = groupId;
}
public Boolean getStatusPrivate() {
return statusPrivate;
}
public void setStatusPrivate(Boolean statusPrivate) {
this.statusPrivate = statusPrivate;
}
public Boolean getHasAttachments() {
return hasAttachments;
}
public void setHasAttachments(Boolean hasAttachments) {
this.hasAttachments = hasAttachments;
}
public Collection getAttachments() {
return attachments;
}
public void setAttachments(Collection attachments) {
this.attachments = attachments;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getDiscussionId() {
return discussionId;
}
public void setDiscussionId(String discussionId) {
this.discussionId = discussionId;
}
public String getReplyTo() {
return replyTo;
}
public void setReplyTo(String replyTo) {
this.replyTo = replyTo;
}
public String getReplyToUsername() {
return replyToUsername;
}
public void setReplyToUsername(String replyToUsername) {
this.replyToUsername = replyToUsername;
}
public boolean isDetailsAvailable() {
return detailsAvailable;
}
public void setDetailsAvailable(boolean detailsAvailable) {
this.detailsAvailable = detailsAvailable;
}
public Boolean getRemoved() {
return removed;
}
public void setRemoved(Boolean removed) {
this.removed = removed;
}
@Override
public String toString() {
return "Status{" +
"groupId='" + groupId + '\'' +
", statusPrivate=" + statusPrivate +
", hasAttachments=" + hasAttachments +
", attachments=" + attachments +
", content='" + content + '\'' +
", discussionId='" + discussionId + '\'' +
", replyTo='" + replyTo + '\'' +
", replyToUsername='" + replyToUsername + '\'' +
", detailsAvailable=" + detailsAvailable +
", removed=" + removed +
'}';
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/domain/status/StatusDetails.java
================================================
package fr.ippon.tatami.domain.status;
import fr.ippon.tatami.domain.User;
import fr.ippon.tatami.service.dto.StatusDTO;
import java.io.Serializable;
import java.util.Collection;
/**
* Extended information for a status.
* - Lists the discussion related to this status
* - Lists the users who shared this status
*/
public class StatusDetails implements Serializable {
private String statusId;
private Collection discussionStatuses;
private Collection sharedByLogins;
public String getStatusId() {
return statusId;
}
public void setStatusId(String statusId) {
this.statusId = statusId;
}
public Collection getDiscussionStatuses() {
return discussionStatuses;
}
public void setDiscussionStatuses(Collection discussionStatuses) {
this.discussionStatuses = discussionStatuses;
}
public Collection getSharedByLogins() {
return sharedByLogins;
}
public void setSharedByLogins(Collection sharedByLogins) {
this.sharedByLogins = sharedByLogins;
}
@Override
public String toString() {
return "StatusDetails{" +
"StatusId='" + statusId + '\'' +
", discussionStatuses=" + discussionStatuses +
", sharedByLogins=" + sharedByLogins +
'}';
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/domain/status/StatusType.java
================================================
package fr.ippon.tatami.domain.status;
public enum StatusType {
STATUS,
SHARE,
ANNOUNCEMENT,
MENTION_FRIEND,
MENTION_SHARE
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/domain/validation/ContraintsAttachmentCreation.java
================================================
package fr.ippon.tatami.domain.validation;
public interface ContraintsAttachmentCreation {
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/domain/validation/ContraintsUserCreation.java
================================================
package fr.ippon.tatami.domain.validation;
/**
* Bean Validation group used to validate the creation of a user.
*
* @author Julien Dubois
*/
public interface ContraintsUserCreation {
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/AppleDeviceRepository.java
================================================
package fr.ippon.tatami.repository;
import java.util.Collection;
public interface AppleDeviceRepository {
void createAppleDevice(String login, String deviceId);
void removeAppleDevice(String login, String deviceId);
Collection findAppleDevices(String login);
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/AppleDeviceUserRepository.java
================================================
package fr.ippon.tatami.repository;
public interface AppleDeviceUserRepository {
void createAppleDeviceForUser(String deviceId, String login);
void removeAppleDeviceForUser(String deviceId);
String findLoginForDeviceId(String deviceId);
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/AttachmentRepository.java
================================================
package fr.ippon.tatami.repository;
import fr.ippon.tatami.domain.Attachment;
public interface AttachmentRepository {
void createAttachment(Attachment attach);
void deleteAttachment(Attachment attach);
/**
* Finds an attachment, including its content (the file data).
*/
Attachment findAttachmentById(String attachmentId);
/**
* Only fetch the attachment metadata : file name & size, but not its content.
*/
Attachment findAttachmentMetadataById(String attachmentId);
/**
* Update the thumbnail of the given attachment
*/
Attachment updateThumbnail(Attachment attach);
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/AvatarRepository.java
================================================
package fr.ippon.tatami.repository;
import fr.ippon.tatami.domain.Avatar;
public interface AvatarRepository {
void createAvatar(Avatar avatar);
void removeAvatar(String avatarId);
Avatar findAvatarById(String avatarId);
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/BlockRepository.java
================================================
package fr.ippon.tatami.repository;
import fr.ippon.tatami.domain.User;
import java.util.Collection;
/**
*This repository stores the the relationships blockingUser-blockedUser.
* Similar strucure as the FriendRepository.
*/
public interface BlockRepository {
void blockUser(String currentUserLogin, String blockedUserLogin);
void unblockUser(String currentUserLogin, String unblockedUserLogin);
Collection getUsersBlockedBy(String userLogin);
boolean isBlocked(String blockingLogin, String blockedLogin);
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/CounterRepository.java
================================================
package fr.ippon.tatami.repository;
/**
* The Counter Repository.
*
* @author Julien Dubois
*/
public interface CounterRepository {
void incrementFollowersCounter(String login);
void incrementFriendsCounter(String login);
void incrementStatusCounter(String login);
void decrementFollowersCounter(String login);
void decrementFriendsCounter(String login);
void decrementStatusCounter(String login);
long getFollowersCounter(String login);
long getFriendsCounter(String login);
long getStatusCounter(String login);
void createFollowersCounter(String login);
void createFriendsCounter(String login);
void createStatusCounter(String login);
void deleteCounters(String login);
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/DaylineRepository.java
================================================
package fr.ippon.tatami.repository;
import fr.ippon.tatami.domain.UserStatusStat;
import fr.ippon.tatami.domain.status.Status;
import java.util.Collection;
/**
* The Dayline Repository, which stores statistics per day.
*
* @author Julien Dubois
*/
public interface DaylineRepository {
/**
* Add a status to the repository.
*/
void addStatusToDayline(Status status, String day);
/**
* Get the statistics for one day, in the form <username, number of status updates>.
*/
Collection getDayline(String domain, String day);
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/DiscussionRepository.java
================================================
package fr.ippon.tatami.repository;
import java.util.Collection;
/**
* The StatusDetails Repository.
*
* @author Julien Dubois
*/
public interface DiscussionRepository {
void addReplyToDiscussion(String originalStatusId, String replyStatusId);
Collection findStatusIdsInDiscussion(String originalStatusId);
boolean hasReply(String statusId);
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/DomainConfigurationRepository.java
================================================
package fr.ippon.tatami.repository;
import fr.ippon.tatami.domain.DomainConfiguration;
/**
* The DomainConfiguraiton Repository.
*
* @author Julien Dubois
*/
public interface DomainConfigurationRepository {
void updateDomainConfiguration(DomainConfiguration domainConfiguration);
DomainConfiguration findDomainConfigurationByDomain(String domain);
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/DomainRepository.java
================================================
package fr.ippon.tatami.repository;
import fr.ippon.tatami.domain.Domain;
import java.util.List;
import java.util.Set;
/**
* The Domain Repository.
*
* @author Julien Dubois
*/
public interface DomainRepository {
void addUserInDomain(String domain, String login);
void updateUserInDomain(String domain, String login);
void deleteUserInDomain(String domain, String login);
List getLoginsInDomain(String domain, int pagination);
List getLoginsInDomain(String domain);
Set getAllDomains();
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/DomainlineRepository.java
================================================
package fr.ippon.tatami.repository;
import java.util.Collection;
import java.util.List;
/**
* The Domainline Repository.
*
* @author Julien Dubois
*/
public interface DomainlineRepository {
/**
* Add a status to the Domain line.
*/
void addStatusToDomainline(String domain, String statusId);
/**
* Remove a collection of statuses from the Domain line.
*/
void removeStatusFromDomainline(String domain, Collection statusIdsToDelete);
/**
* The Domainline : the public status for a domain.
* - The name is the statusId of the statuses
* - Value is always null
*/
List getDomainline(String domain, int size, String start, String finish);
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/FavoritelineRepository.java
================================================
package fr.ippon.tatami.repository;
import java.util.List;
/**
* The Favoriteline Repository.
*
* @author Julien Dubois
*/
public interface FavoritelineRepository {
void addStatusToFavoriteline(String login, String statusId);
void removeStatusFromFavoriteline(String login, String statusId);
void deleteFavoriteline(String login);
/**
* The favoriteline : the statuses fovorited by the user.
* - The key is the statusId of the statuses
* - The value is always null
*/
List getFavoriteline(String login);
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/FollowerRepository.java
================================================
package fr.ippon.tatami.repository;
import java.util.Collection;
/**
* The Follower Repository.
*
* @author Julien Dubois
*/
public interface FollowerRepository {
void addFollower(String login, String followerLogin);
void removeFollower(String login, String followerLogin);
Collection findFollowersForUser(String login);
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/FriendRepository.java
================================================
package fr.ippon.tatami.repository;
import java.util.List;
/**
* The Friend Repository.
*
* @author Julien Dubois
*/
public interface FriendRepository {
void addFriend(String login, String friendLogin);
void removeFriend(String login, String friendLogin);
List findFriendsForUser(String login);
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/GroupCounterRepository.java
================================================
package fr.ippon.tatami.repository;
/**
* The Group Counter Repository.
*
* @author Julien Dubois
*/
public interface GroupCounterRepository {
long getGroupCounter(String domain, String groupId);
void incrementGroupCounter(String domain, String groupId);
void decrementGroupCounter(String domain, String groupId);
void deleteGroupCounter(String domain, String groupId);
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/GroupDetailsRepository.java
================================================
package fr.ippon.tatami.repository;
import fr.ippon.tatami.domain.Group;
/**
* The Group Details Repository.
*
* @author Julien Dubois
*/
public interface GroupDetailsRepository {
void createGroupDetails(String groupId, String name, String description, boolean publicGroup);
Group getGroupDetails(String groupId);
void editGroupDetails(String groupId, String name, String description, boolean archivedGroup);
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/GroupMembersRepository.java
================================================
package fr.ippon.tatami.repository;
import java.util.Map;
/**
* The Group members Repository.
*
* @author Julien Dubois
*/
public interface GroupMembersRepository {
void addMember(String groupId, String login);
void addAdmin(String groupId, String login);
void removeMember(String groupId, String login);
Map findMembers(String groupId);
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/GroupRepository.java
================================================
package fr.ippon.tatami.repository;
import fr.ippon.tatami.domain.Group;
/**
* The Group Repository.
*
* @author Julien Dubois
*/
public interface GroupRepository {
String createGroup(String domain);
Group getGroupById(String domain, String groupId);
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/GrouplineRepository.java
================================================
package fr.ippon.tatami.repository;
import java.util.Collection;
import java.util.List;
/**
* The Groupline Repository.
*
* @author Julien Dubois
*/
public interface GrouplineRepository {
/**
* Add a status to the Group line.
*/
void addStatusToGroupline(String groupId, String statusId);
/**
* Remove a collection of statuses from the Group line.
*/
void removeStatusesFromGroupline(String groupId, Collection statusIdsToDelete);
/**
* The Groupline : the statuses for a given group.
* - The name is the statusId of the statuses
* - Value is always null
*/
List getGroupline(String groupId, int size, String start, String finish);
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/IdempotentRepository.java
================================================
package fr.ippon.tatami.repository;
/**
* Used to de-deplucate Camel messages.
*/
public interface IdempotentRepository extends org.apache.camel.spi.IdempotentRepository {
@Override
boolean add(String key);
@Override
boolean contains(String key);
@Override
boolean remove(String key);
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/MailDigestRepository.java
================================================
package fr.ippon.tatami.repository;
import fr.ippon.tatami.domain.DigestType;
import java.util.List;
/**
* Provide access to digest subscription info.
*
* Subscription are organized by digest type and domain (in order to allow
* domain-level configuration).
*
* @author Pierre Rust
*/
public interface MailDigestRepository {
/**
* Subscribe an user to email digest.
*/
void subscribeToDigest(DigestType digestType, String login, String domain, String day);
/**
* Un-subscribe an user from a domain.
*/
void unsubscribeFromDigest(DigestType digestType, String login, String domain, String day);
/**
* Retrieves the list of logins in a domain subscribed to a given digest type.
*/
List getLoginsRegisteredToDigest(DigestType digestType, String domain, String day, int pagination);
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/MentionlineRepository.java
================================================
package fr.ippon.tatami.repository;
import java.util.Collection;
import java.util.List;
/**
* The Mentionline Repository.
*
* @author Julien Dubois
*/
public interface MentionlineRepository {
/**
* Add a status to the Mention line.
*/
void addStatusToMentionline(String mentionedLogin, String statusId);
/**
* Remove a collection of statuses from the Mention line.
*/
void removeStatusesFromMentionline(String mentionedLogin, Collection statusIdsToDelete);
/**
* The mention line : the mentions for a given user.
* - The name is the statusId of the statuses
* - Value is always null
*/
List getMentionline(String login, int size, String start, String finish);
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/RegistrationRepository.java
================================================
package fr.ippon.tatami.repository;
import java.util.Map;
/**
* The Registration Repository.
*
* @author Julien Dubois
*/
public interface RegistrationRepository {
String generateRegistrationKey(String login);
String getLoginByRegistrationKey(String registrationKey);
/**
* !! For testing purpose only !!
*/
Map _getAllRegistrationKeyByLogin();
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/ResolvedReportRepository.java
================================================
package fr.ippon.tatami.repository;
import java.util.Collection;
/**
* Created by emilyklein on 7/13/16.
*/
public interface ResolvedReportRepository {
void resolvedReport (String reportingUser, String reportedStatusId, Long timeReported, String adminResolved, String actionTaken);
Collection findResolvedReportsById(String statusId);
boolean hasBeenResolved(String statusId);
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/RssUidRepository.java
================================================
package fr.ippon.tatami.repository;
/**
* The Rss Uid Repository.
*
* The RSS uid is used to build an anonymous url to a RSS channel
* representing an user timeline.
*
* @author Pierre Rust
*/
public interface RssUidRepository {
String generateRssUid(String login);
void removeRssUid(String rssUid);
String getLoginByRssUid(String rssUid);
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/SharesRepository.java
================================================
package fr.ippon.tatami.repository;
import java.util.Collection;
/**
* The StatusDetails Repository.
*
* @author Julien Dubois
*/
public interface SharesRepository {
void newShareByLogin(String statusId, String sharedByLogin);
Collection findLoginsWhoSharedAStatus(String statusId);
boolean hasBeenShared(String statusId);
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/StatusAttachmentRepository.java
================================================
package fr.ippon.tatami.repository;
import java.util.Collection;
/**
* Stores attachment IDs for a status.
*/
public interface StatusAttachmentRepository {
void addAttachmentId(String statusId, String attachmentId);
void removeAttachmentId(String statusId, String attachmentId);
Collection findAttachmentIds(String statusId);
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/StatusReportRepository.java
================================================
package fr.ippon.tatami.repository;
import java.util.Collection;
import java.util.List;
public interface StatusReportRepository {
void reportStatus (String domain, String reportedStatusId, String reportingLogin);
void unreportStatus (String domain, String reportedStatusId);
List findReportedStatuses(String domain);
String findUserHavingReported(String domain, String statusId);
boolean hasBeenReportedByUser(String domain, String reportedStatusId, String login);
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/StatusRepository.java
================================================
package fr.ippon.tatami.repository;
import fr.ippon.tatami.domain.Group;
import fr.ippon.tatami.domain.status.*;
import javax.validation.ConstraintViolationException;
import java.util.Collection;
/**
* The Status Repository.
*
* @author Julien Dubois
*/
public interface StatusRepository {
Status createStatus(String login,
boolean statusPrivate,
Group group,
Collection attachmentIds,
String content,
String discussionId,
String replyTo,
String replyToUsername,
String geoLocalization) throws ConstraintViolationException;
Share createShare(String login,
String originalStatusId);
Announcement createAnnouncement(String login,
String originalStatusId);
MentionFriend createMentionFriend(String login,
String followerLogin);
MentionShare createMentionShare(String login,
String originalStatusId);
void removeStatus(AbstractStatus status);
/**
* Retrieve a persisted status.
*
* @return null if status was removed
*/
AbstractStatus findStatusById(String statusId);
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/TagCounterRepository.java
================================================
package fr.ippon.tatami.repository;
/**
* The Tag Counter Repository.
*
* @author Julien Dubois
*/
public interface TagCounterRepository {
long getTagCounter(String domain, String tag);
void incrementTagCounter(String domain, String tag);
void decrementTagCounter(String domain, String tag);
void deleteTagCounter(String domain, String tag);
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/TagFollowerRepository.java
================================================
package fr.ippon.tatami.repository;
import java.util.Collection;
/**
* Specific Follower repository for tags.
*/
public interface TagFollowerRepository {
void addFollower(String domain, String tag, String login);
void removeFollower(String domain, String tag, String login);
Collection findFollowers(String domain, String tag);
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/TaglineRepository.java
================================================
package fr.ippon.tatami.repository;
import fr.ippon.tatami.domain.status.Status;
import java.util.Collection;
import java.util.List;
/**
* The Tagline Repository.
*
* @author Julien Dubois
*/
public interface TaglineRepository {
/**
* Add a status to the Tag line.
*/
void addStatusToTagline(String tag, Status status);
/**
* Remove a collection of statuses from the Tag line.
*/
void removeStatusesFromTagline(String tag, String domain, Collection statusIdsToDelete);
/**
* The tagline : the statuses for a given tag.
* - The name is the statusId of the statuses
* - Value is always null
*/
List getTagline(String domain, String tag, int size, String start, String finish);
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/TimelineRepository.java
================================================
package fr.ippon.tatami.repository;
import fr.ippon.tatami.domain.status.Announcement;
import fr.ippon.tatami.domain.status.Share;
import java.util.Collection;
import java.util.List;
/**
* The Timeline Repository.
*
* A Timeline is the list of statuses that a user sees, which includes :
* - The statuses from the people he follows
* - His own statuses
* - Statuses that were shared by the people he follows or by himself
*
* @author Julien Dubois
*/
public interface TimelineRepository {
boolean isStatusInTimeline(String login, String statusId);
void addStatusToTimeline(String login, String statusId);
void removeStatusesFromTimeline(String login, Collection statusIdsToDelete);
void shareStatusToTimeline(String sharedByLogin, String timelineLogin, Share share);
void announceStatusToTimeline(String announcedByLogin, List logins, Announcement announcement);
void deleteTimeline(String login);
/**
* The user timeline : the user's statuses, and statuses from users he follows.
* - The key is the statusId of the statuses
* - The value is always null
*/
List getTimeline(String login, int size, String start, String finish);
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/TrendRepository.java
================================================
package fr.ippon.tatami.repository;
import java.util.Collection;
import java.util.List;
/**
* The Trends repository : stores and retrieves tags trends.
*/
public interface TrendRepository {
void addTag(String domain, String tag);
List getRecentTags(String domain);
List getRecentTags(String domain, int maxNumber);
Collection getDomainTags(String domain);
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/UserAttachmentRepository.java
================================================
package fr.ippon.tatami.repository;
import java.util.Collection;
/**
* Stores attachment IDs for a user.
*/
public interface UserAttachmentRepository {
void addAttachmentId(String login, String attachmentId);
void removeAttachmentId(String login, String attachmentId);
Collection findAttachmentIds(String login, int pagination, String finish);
Collection findAttachmentIds(String login);
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/UserGroupRepository.java
================================================
package fr.ippon.tatami.repository;
import java.util.Collection;
import java.util.List;
/**
* The User group Repository.
*
* @author Julien Dubois
*/
public interface UserGroupRepository {
void addGroupAsMember(String login, String groupId);
void addGroupAsAdmin(String login, String groupId);
void removeGroup(String login, String groupId);
List findGroups(String login);
Collection findGroupsAsAdmin(String login);
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/UserRepository.java
================================================
package fr.ippon.tatami.repository;
import fr.ippon.tatami.domain.User;
import javax.validation.ConstraintViolationException;
/**
* The User Repository.
*
* @author Julien Dubois
*/
public interface UserRepository {
void createUser(User user);
void updateUser(User user) throws ConstraintViolationException, IllegalArgumentException;
void deleteUser(User user);
void desactivateUser( User user );
void reactivateUser( User user );
User findUserByLogin(String login);
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/UserTagRepository.java
================================================
package fr.ippon.tatami.repository;
import java.util.Collection;
/**
* Specific Friend repository for tags.
*/
public interface UserTagRepository {
void addTag(String login, String tag);
void removeTag(String login, String tag);
Collection findTags(String login);
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/UserTrendRepository.java
================================================
package fr.ippon.tatami.repository;
import java.util.Collection;
import java.util.Date;
import java.util.List;
/**
* The User Trends repository : stores user trends.
*/
public interface UserTrendRepository {
void addTag(String login, String tag);
List getRecentTags(String login);
Collection getUserRecentTags(String login, Date endDate, int nbRecentTags);
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/UserlineRepository.java
================================================
package fr.ippon.tatami.repository;
import fr.ippon.tatami.domain.status.Share;
import java.util.Collection;
import java.util.List;
/**
* The Userline Repository.
*
* A Userline is the list of statuses updated by a user (including the statuses he shared).
*
* @author Julien Dubois
*/
public interface UserlineRepository {
/**
* Add a status to the user line.
*/
void addStatusToUserline(String login, String statusId);
/**
* Remove a collection of statuses from the user line.
*/
void removeStatusesFromUserline(String login, Collection statusIdsToDelete);
void shareStatusToUserline(String currentLogin, Share share);
void deleteUserline(String login);
/**
* The userline : the user's statuses.
* - The key is the statusId of the statuses
* - The value is always null
*/
List getUserline(String login, int size, String start, String finish);
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/cassandra/AbstractCassandraFollowerRepository.java
================================================
package fr.ippon.tatami.repository.cassandra;
import fr.ippon.tatami.config.Constants;
import me.prettyprint.cassandra.serializers.LongSerializer;
import me.prettyprint.cassandra.serializers.StringSerializer;
import me.prettyprint.cassandra.service.template.ColumnFamilyResult;
import me.prettyprint.cassandra.service.template.ColumnFamilyTemplate;
import me.prettyprint.cassandra.service.template.ThriftColumnFamilyTemplate;
import me.prettyprint.hector.api.Keyspace;
import me.prettyprint.hector.api.factory.HFactory;
import me.prettyprint.hector.api.mutation.Mutator;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
/**
* Abstract class for managing followers : users who follow another user or a tag.
*/
public abstract class AbstractCassandraFollowerRepository {
private ColumnFamilyTemplate template;
@Inject
private Keyspace keyspaceOperator;
@PostConstruct
public void init() {
template = new ThriftColumnFamilyTemplate(keyspaceOperator,
getFollowersCF(),
StringSerializer.get(),
StringSerializer.get());
template.setCount(Constants.CASSANDRA_MAX_COLUMNS);
}
void addFollower(String key, String followerKey) {
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.insert(key, getFollowersCF(), HFactory.createColumn(followerKey,
Calendar.getInstance().getTimeInMillis(), StringSerializer.get(), LongSerializer.get()));
}
void removeFollower(String key, String followerKey) {
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.delete(key, getFollowersCF(), followerKey, StringSerializer.get());
}
Collection findFollowers(String key) {
ColumnFamilyResult result = template.queryColumns(key);
Collection followers = new ArrayList();
for (String columnName : result.getColumnNames()) {
followers.add(columnName);
}
return followers;
}
protected abstract String getFollowersCF();
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/cassandra/AbstractCassandraFriendRepository.java
================================================
package fr.ippon.tatami.repository.cassandra;
import fr.ippon.tatami.config.Constants;
import me.prettyprint.cassandra.serializers.LongSerializer;
import me.prettyprint.cassandra.serializers.StringSerializer;
import me.prettyprint.cassandra.service.template.ColumnFamilyResult;
import me.prettyprint.cassandra.service.template.ColumnFamilyTemplate;
import me.prettyprint.cassandra.service.template.ThriftColumnFamilyTemplate;
import me.prettyprint.hector.api.Keyspace;
import me.prettyprint.hector.api.factory.HFactory;
import me.prettyprint.hector.api.mutation.Mutator;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
/**
* Abstract class for managing friends : users or tags that a user follows.
*/
public abstract class AbstractCassandraFriendRepository {
private ColumnFamilyTemplate friendsTemplate;
@Inject
private Keyspace keyspaceOperator;
@PostConstruct
public void init() {
friendsTemplate = new ThriftColumnFamilyTemplate(keyspaceOperator,
getFriendsCF(),
StringSerializer.get(),
StringSerializer.get());
friendsTemplate.setCount(Constants.CASSANDRA_MAX_COLUMNS);
}
void addFriend(String key, String friendKey) {
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.insert(key, getFriendsCF(), HFactory.createColumn(friendKey,
Calendar.getInstance().getTimeInMillis(), StringSerializer.get(), LongSerializer.get()));
}
void removeFriend(String key, String friendKey) {
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.delete(key, getFriendsCF(), friendKey, StringSerializer.get());
}
List findFriends(String key) {
ColumnFamilyResult result = friendsTemplate.queryColumns(key);
List friends = new ArrayList();
for (String columnName : result.getColumnNames()) {
friends.add(columnName);
}
return friends;
}
protected abstract String getFriendsCF();
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/cassandra/AbstractCassandraLineRepository.java
================================================
package fr.ippon.tatami.repository.cassandra;
import fr.ippon.tatami.domain.status.Share;
import me.prettyprint.cassandra.serializers.StringSerializer;
import me.prettyprint.cassandra.serializers.UUIDSerializer;
import me.prettyprint.hector.api.Keyspace;
import me.prettyprint.hector.api.beans.ColumnSlice;
import me.prettyprint.hector.api.beans.HColumn;
import me.prettyprint.hector.api.factory.HFactory;
import me.prettyprint.hector.api.mutation.Mutator;
import me.prettyprint.hector.api.query.ColumnQuery;
import me.prettyprint.hector.api.query.QueryResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.UUID;
import static me.prettyprint.hector.api.factory.HFactory.createSliceQuery;
/**
* This abstract class contains commun functions for Timeline and Userline.
*
* Timeline and Userline have the same structure :
* - Key : login
* - Name : status Id
* - Value : ""
*
* @author Julien Dubois
*/
public abstract class AbstractCassandraLineRepository {
private final Logger log = LoggerFactory.getLogger(AbstractCassandraLineRepository.class);
@Inject
protected Keyspace keyspaceOperator;
/**
* Add a status to the CF.
*/
protected void addStatus(String key, String cf, String statusId) {
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.insert(key, cf, HFactory.createColumn(UUID.fromString(statusId),
"", UUIDSerializer.get(), StringSerializer.get()));
}
/**
* Add a status with a time-to-live.
*/
protected void addStatus(String key, String cf, String statusId, int ttl) {
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.insert(key, cf, HFactory.createColumn(UUID.fromString(statusId),
"", ttl, UUIDSerializer.get(), StringSerializer.get()));
}
/**
* Remove a collection of statuses.
*/
protected void removeStatuses(String key, String cf, Collection statusIdsToDelete) {
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
for (String statusId : statusIdsToDelete) {
mutator.addDeletion(key, cf, UUID.fromString(statusId), UUIDSerializer.get());
}
mutator.execute();
}
List getLineFromCF(String cf, String login, int size, String start, String finish) {
List> result;
if (finish != null) {
ColumnSlice query = createSliceQuery(keyspaceOperator,
StringSerializer.get(), UUIDSerializer.get(), StringSerializer.get())
.setColumnFamily(cf)
.setKey(login)
.setRange(UUID.fromString(finish), null, true, size)
.execute()
.get();
result = query.getColumns().subList(1, query.getColumns().size());
} else if (start != null) {
ColumnSlice query = createSliceQuery(keyspaceOperator,
StringSerializer.get(), UUIDSerializer.get(), StringSerializer.get())
.setColumnFamily(cf)
.setKey(login)
.setRange(null, UUID.fromString(start), true, size)
.execute()
.get();
int maxIndex = query.getColumns().size() - 1;
if (maxIndex < 0) {
maxIndex = 0;
}
result = query.getColumns().subList(0, maxIndex);
} else {
ColumnSlice query = createSliceQuery(keyspaceOperator,
StringSerializer.get(), UUIDSerializer.get(), StringSerializer.get())
.setColumnFamily(cf)
.setKey(login)
.setRange(null, null, true, size)
.execute()
.get();
result = query.getColumns();
}
List line = new ArrayList();
for (HColumn column : result) {
line.add(column.getName().toString());
}
return line;
}
void shareStatus(String login,
Share share,
String columnFamily,
String sharesColumnFamily) {
QueryResult> isStatusAlreadyinTimeline =
findByLoginAndStatusId(columnFamily, login, UUID.fromString(share.getOriginalStatusId()));
if (isStatusAlreadyinTimeline.get() == null) {
QueryResult> isStatusAlreadyShared =
findByLoginAndStatusId(sharesColumnFamily, login, UUID.fromString(share.getOriginalStatusId()));
if (isStatusAlreadyShared.get() == null) {
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.insert(login, columnFamily, HFactory.createColumn(UUID.fromString(share.getStatusId()),
"", UUIDSerializer.get(), StringSerializer.get()));
mutator.insert(login, sharesColumnFamily, HFactory.createColumn(UUID.fromString(share.getOriginalStatusId()),
"", UUIDSerializer.get(), StringSerializer.get()));
} else {
log.debug("Shared status {} is already shared in {}", share.getOriginalStatusId(), columnFamily);
}
} else {
log.debug("Shared status {} is already present in {}", share.getOriginalStatusId(), columnFamily);
}
}
QueryResult> findByLoginAndStatusId(String columnFamily, String login, UUID statusId) {
ColumnQuery columnQuery =
HFactory.createColumnQuery(keyspaceOperator, StringSerializer.get(),
UUIDSerializer.get(), StringSerializer.get());
columnQuery.setColumnFamily(columnFamily).setKey(login).setName(statusId);
return columnQuery.execute();
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/cassandra/CassandraAppleDeviceRepository.java
================================================
package fr.ippon.tatami.repository.cassandra;
import fr.ippon.tatami.repository.AppleDeviceRepository;
import me.prettyprint.cassandra.serializers.StringSerializer;
import me.prettyprint.hector.api.Keyspace;
import me.prettyprint.hector.api.beans.ColumnSlice;
import me.prettyprint.hector.api.beans.HColumn;
import me.prettyprint.hector.api.factory.HFactory;
import me.prettyprint.hector.api.mutation.Mutator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Repository;
import fr.ippon.tatami.config.ColumnFamilyKeys;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.Collection;
import static me.prettyprint.hector.api.factory.HFactory.createSliceQuery;
/**
* Cassandra implementation of the AppleDevice repository.
*
* Maps users to Apple device ids.
*
* Structure :
* - Key = login
* - Name = apple device id
* - Value = ""
*
* @author Julien Dubois
*/
@Repository
public class CassandraAppleDeviceRepository implements AppleDeviceRepository {
private final Logger log = LoggerFactory.getLogger(CassandraAppleDeviceRepository.class);
@Inject
private Keyspace keyspaceOperator;
@Override
public void createAppleDevice(String login, String deviceId) {
log.debug("Creating Apple Device for user {} : {}", login, deviceId);
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.insert(login, ColumnFamilyKeys.APPLE_DEVICE_CF, HFactory.createColumn(deviceId,
"", StringSerializer.get(), StringSerializer.get()));
}
@Override
public void removeAppleDevice(String login, String deviceId) {
log.debug("Deleting Apple Device for user {} : {}", login, deviceId);
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.delete(login, ColumnFamilyKeys.APPLE_DEVICE_CF, deviceId, StringSerializer.get());
}
@Override
public Collection findAppleDevices(String login) {
Collection deviceIds = new ArrayList();
ColumnSlice result = createSliceQuery(keyspaceOperator,
StringSerializer.get(), StringSerializer.get(), StringSerializer.get())
.setColumnFamily(ColumnFamilyKeys.APPLE_DEVICE_CF)
.setKey(login)
.setRange(null, null, false, Integer.MAX_VALUE)
.execute()
.get();
for (HColumn column : result.getColumns()) {
deviceIds.add(column.getName());
}
return deviceIds;
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/cassandra/CassandraAppleDeviceUserRepository.java
================================================
package fr.ippon.tatami.repository.cassandra;
import fr.ippon.tatami.repository.AppleDeviceUserRepository;
import me.prettyprint.cassandra.serializers.StringSerializer;
import me.prettyprint.hector.api.Keyspace;
import me.prettyprint.hector.api.beans.HColumn;
import me.prettyprint.hector.api.factory.HFactory;
import me.prettyprint.hector.api.mutation.Mutator;
import me.prettyprint.hector.api.query.ColumnQuery;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Repository;
import javax.inject.Inject;
import static fr.ippon.tatami.config.ColumnFamilyKeys.APPLE_DEVICE_USER_CF;
/**
* Cassandra implementation of the AppleDeviceUser repository.
*
* Maps Apple device ids to users.
*
* Structure :
* - Key = apple device id
* - Name = USER_LOGIN
* - Value = user login
*
* @author Julien Dubois
*/
@Repository
public class CassandraAppleDeviceUserRepository implements AppleDeviceUserRepository {
private final Logger log = LoggerFactory.getLogger(CassandraAppleDeviceUserRepository.class);
private static final String USER_LOGIN = "USER_LOGIN";
@Inject
private Keyspace keyspaceOperator;
@Override
public void createAppleDeviceForUser(String deviceId, String login) {
log.debug("Mapping Apple device id to user {} : {}", deviceId, login);
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.insert(deviceId, APPLE_DEVICE_USER_CF, HFactory.createColumn(USER_LOGIN,
login, StringSerializer.get(), StringSerializer.get()));
}
@Override
public void removeAppleDeviceForUser(String deviceId) {
log.debug("Removing mapping of Apple device id {}", deviceId);
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.addDeletion(deviceId, APPLE_DEVICE_USER_CF);
mutator.execute();
}
@Override
public String findLoginForDeviceId(String deviceId) {
log.debug("Finding user of Apple device id {}", deviceId);
ColumnQuery query = HFactory.createStringColumnQuery(keyspaceOperator);
HColumn column =
query.setColumnFamily(APPLE_DEVICE_USER_CF)
.setKey(deviceId)
.setName(USER_LOGIN)
.execute()
.get();
if (column != null) {
return column.getValue();
} else {
return null;
}
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/cassandra/CassandraAttachmentRepository.java
================================================
package fr.ippon.tatami.repository.cassandra;
import fr.ippon.tatami.domain.Attachment;
import fr.ippon.tatami.repository.AttachmentRepository;
import me.prettyprint.cassandra.serializers.BytesArraySerializer;
import me.prettyprint.cassandra.serializers.DateSerializer;
import me.prettyprint.cassandra.serializers.LongSerializer;
import me.prettyprint.cassandra.serializers.StringSerializer;
import me.prettyprint.cassandra.utils.TimeUUIDUtils;
import me.prettyprint.hector.api.Keyspace;
import me.prettyprint.hector.api.beans.HColumn;
import me.prettyprint.hector.api.factory.HFactory;
import me.prettyprint.hector.api.mutation.Mutator;
import me.prettyprint.hector.api.query.ColumnQuery;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Repository;
import javax.inject.Inject;
import java.util.Date;
import static fr.ippon.tatami.config.ColumnFamilyKeys.ATTACHMENT_CF;
@Repository
public class CassandraAttachmentRepository implements AttachmentRepository {
private final Logger log = LoggerFactory.getLogger(CassandraAttachmentRepository.class);
private final String CONTENT = "content";
private final String THUMBNAIL = "thumbnail";
private final String FILENAME = "filename";
private final String SIZE = "size";
private final String CREATION_DATE = "creation_date";
@Inject
private Keyspace keyspaceOperator;
@Override
public void createAttachment(Attachment attachment) {
String attachmentId = TimeUUIDUtils.getUniqueTimeUUIDinMillis().toString();
log.debug("Creating attachment : {}", attachment);
attachment.setAttachmentId(attachmentId);
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.insert(attachmentId, ATTACHMENT_CF, HFactory.createColumn(CONTENT,
attachment.getContent(), StringSerializer.get(), BytesArraySerializer.get()));
mutator.insert(attachmentId, ATTACHMENT_CF, HFactory.createColumn(THUMBNAIL,
attachment.getThumbnail(), StringSerializer.get(), BytesArraySerializer.get()));
mutator.insert(attachmentId, ATTACHMENT_CF, HFactory.createColumn(FILENAME,
attachment.getFilename(), StringSerializer.get(), StringSerializer.get()));
mutator.insert(attachmentId, ATTACHMENT_CF, HFactory.createColumn(SIZE,
attachment.getSize(), StringSerializer.get(), LongSerializer.get()));
mutator.insert(attachmentId, ATTACHMENT_CF, HFactory.createColumn(CREATION_DATE,
attachment.getCreationDate(), StringSerializer.get(), DateSerializer.get()));
}
@Override
@CacheEvict(value = "attachment-cache", key = "#attachment.attachmentId")
public void deleteAttachment(Attachment attachment) {
log.debug("Deleting attachment : {}", attachment);
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.addDeletion(attachment.getAttachmentId(), ATTACHMENT_CF);
mutator.execute();
}
@Override
@Cacheable("attachment-cache")
public Attachment findAttachmentById(String attachmentId) {
if (attachmentId == null) {
return null;
}
log.debug("Finding attachment : {}", attachmentId);
Attachment attachment = this.findAttachmentMetadataById(attachmentId);
if (attachment == null) {
return null;
}
ColumnQuery queryAttachment = HFactory.createColumnQuery(keyspaceOperator,
StringSerializer.get(), StringSerializer.get(), BytesArraySerializer.get());
HColumn columnAttachment =
queryAttachment.setColumnFamily(ATTACHMENT_CF)
.setKey(attachmentId)
.setName(CONTENT)
.execute()
.get();
attachment.setContent(columnAttachment.getValue());
ColumnQuery queryThumbnail = HFactory.createColumnQuery(keyspaceOperator,
StringSerializer.get(), StringSerializer.get(), BytesArraySerializer.get());
HColumn columnThumbnail =
queryThumbnail.setColumnFamily(ATTACHMENT_CF)
.setKey(attachmentId)
.setName(THUMBNAIL)
.execute()
.get();
if(columnThumbnail != null && columnThumbnail.getValue().length > 0) {
attachment.setThumbnail(columnThumbnail.getValue());
attachment.setHasThumbnail(true);
}
else {
attachment.setHasThumbnail(false);
}
return attachment;
}
@Override
public Attachment findAttachmentMetadataById(String attachmentId) {
if (attachmentId == null) {
return null;
}
Attachment attachment = new Attachment();
attachment.setAttachmentId(attachmentId);
ColumnQuery queryFilename = HFactory.createColumnQuery(keyspaceOperator,
StringSerializer.get(), StringSerializer.get(), StringSerializer.get());
HColumn columnFilename =
queryFilename.setColumnFamily(ATTACHMENT_CF)
.setKey(attachmentId)
.setName(FILENAME)
.execute()
.get();
if (columnFilename != null && columnFilename.getValue() != null) {
attachment.setFilename(columnFilename.getValue());
} else {
return null;
}
ColumnQuery querySize = HFactory.createColumnQuery(keyspaceOperator,
StringSerializer.get(), StringSerializer.get(), LongSerializer.get());
HColumn columnSize =
querySize.setColumnFamily(ATTACHMENT_CF)
.setKey(attachmentId)
.setName(SIZE)
.execute()
.get();
if (columnSize != null && columnSize.getValue() != null) {
attachment.setSize(columnSize.getValue());
} else {
return null;
}
ColumnQuery queryCreationDate = HFactory.createColumnQuery(keyspaceOperator,
StringSerializer.get(), StringSerializer.get(), DateSerializer.get());
HColumn columnCreationDate =
queryCreationDate.setColumnFamily(ATTACHMENT_CF)
.setKey(attachmentId)
.setName(CREATION_DATE)
.execute()
.get();
if (columnCreationDate != null && columnCreationDate.getValue() != null) {
attachment.setCreationDate(columnCreationDate.getValue());
} else {
attachment.setCreationDate(new Date());
}
return attachment;
}
@Override
public Attachment updateThumbnail(Attachment attach) {
log.debug("Updating thumbnail : {}", attach);
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.insert(attach.getAttachmentId(), ATTACHMENT_CF, HFactory.createColumn(THUMBNAIL,
attach.getThumbnail(), StringSerializer.get(), BytesArraySerializer.get()));
return attach;
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/cassandra/CassandraAvatarRepository.java
================================================
package fr.ippon.tatami.repository.cassandra;
import fr.ippon.tatami.domain.Avatar;
import fr.ippon.tatami.repository.AvatarRepository;
import me.prettyprint.cassandra.serializers.BytesArraySerializer;
import me.prettyprint.cassandra.serializers.DateSerializer;
import me.prettyprint.cassandra.serializers.LongSerializer;
import me.prettyprint.cassandra.serializers.StringSerializer;
import me.prettyprint.cassandra.utils.TimeUUIDUtils;
import me.prettyprint.hector.api.Keyspace;
import me.prettyprint.hector.api.beans.HColumn;
import me.prettyprint.hector.api.factory.HFactory;
import me.prettyprint.hector.api.mutation.Mutator;
import me.prettyprint.hector.api.query.ColumnQuery;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Repository;
import javax.inject.Inject;
import java.util.Date;
import static fr.ippon.tatami.config.ColumnFamilyKeys.AVATAR_CF;
@Repository
public class CassandraAvatarRepository implements AvatarRepository {
private final Logger log = LoggerFactory.getLogger(CassandraAttachmentRepository.class);
private final String CONTENT = "content";
private final String FILENAME = "filename";
private final String SIZE = "size";
private final String CREATION_DATE = "creation_date";
@Inject
private Keyspace keyspaceOperator;
@Override
public void createAvatar(Avatar avatar) {
String avatarId = TimeUUIDUtils.getUniqueTimeUUIDinMillis().toString();
log.debug("Creating avatar : {}", avatar);
avatar.setAvatarId(avatarId);
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.insert(avatarId, AVATAR_CF, HFactory.createColumn(CONTENT,
avatar.getContent(), StringSerializer.get(), BytesArraySerializer.get()));
mutator.insert(avatarId, AVATAR_CF, HFactory.createColumn(FILENAME,
avatar.getFilename(), StringSerializer.get(), StringSerializer.get()));
mutator.insert(avatarId, AVATAR_CF, HFactory.createColumn(SIZE,
avatar.getSize(), StringSerializer.get(), LongSerializer.get()));
mutator.insert(avatarId, AVATAR_CF, HFactory.createColumn(CREATION_DATE,
avatar.getCreationDate(), StringSerializer.get(), DateSerializer.get()));
}
@Override
@CacheEvict(value = "avatar-cache")
public void removeAvatar(String avatarId) {
log.debug("Avatar deleted : {}", avatarId);
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.addDeletion(avatarId, AVATAR_CF);
mutator.execute();
}
@Override
@Cacheable("avatar-cache")
public Avatar findAvatarById(String avatarId) {
if (avatarId == null) {
return null;
}
log.debug("Finding avatar : {}", avatarId);
Avatar avatar = this.findAttachmentMetadataById(avatarId);
if (avatar == null) {
return null;
}
ColumnQuery queryAttachment = HFactory.createColumnQuery(keyspaceOperator,
StringSerializer.get(), StringSerializer.get(), BytesArraySerializer.get());
HColumn columnAttachment =
queryAttachment.setColumnFamily(AVATAR_CF)
.setKey(avatarId)
.setName(CONTENT)
.execute()
.get();
avatar.setContent(columnAttachment.getValue());
return avatar;
}
Avatar findAttachmentMetadataById(String avatarId) {
if (avatarId == null) {
return null;
}
Avatar avatar = new Avatar();
avatar.setAvatarId(avatarId);
ColumnQuery queryFilename = HFactory.createColumnQuery(keyspaceOperator,
StringSerializer.get(), StringSerializer.get(), StringSerializer.get());
HColumn columnFilename =
queryFilename.setColumnFamily(AVATAR_CF)
.setKey(avatarId)
.setName(FILENAME)
.execute()
.get();
if (columnFilename != null && columnFilename.getValue() != null) {
avatar.setFilename(columnFilename.getValue());
} else {
return null;
}
ColumnQuery querySize = HFactory.createColumnQuery(keyspaceOperator,
StringSerializer.get(), StringSerializer.get(), LongSerializer.get());
HColumn columnSize =
querySize.setColumnFamily(AVATAR_CF)
.setKey(avatarId)
.setName(SIZE)
.execute()
.get();
if (columnSize != null && columnSize.getValue() != null) {
avatar.setSize(columnSize.getValue());
} else {
return null;
}
ColumnQuery queryCreationDate = HFactory.createColumnQuery(keyspaceOperator,
StringSerializer.get(), StringSerializer.get(), DateSerializer.get());
HColumn columnCreationDate =
queryCreationDate.setColumnFamily(AVATAR_CF)
.setKey(avatarId)
.setName(CREATION_DATE)
.execute()
.get();
if (columnCreationDate != null && columnCreationDate.getValue() != null) {
avatar.setCreationDate(columnCreationDate.getValue());
} else {
avatar.setCreationDate(new Date());
}
return avatar;
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/cassandra/CassandraBlockRepository.java
================================================
package fr.ippon.tatami.repository.cassandra;
import fr.ippon.tatami.config.Constants;
import fr.ippon.tatami.repository.BlockRepository;
import me.prettyprint.cassandra.serializers.LongSerializer;
import me.prettyprint.cassandra.serializers.StringSerializer;
import me.prettyprint.cassandra.service.template.ColumnFamilyResult;
import me.prettyprint.cassandra.service.template.ColumnFamilyTemplate;
import me.prettyprint.cassandra.service.template.ThriftColumnFamilyTemplate;
import me.prettyprint.hector.api.Keyspace;
import me.prettyprint.hector.api.factory.HFactory;
import me.prettyprint.hector.api.mutation.Mutator;
import org.springframework.stereotype.Repository;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.List;
import static fr.ippon.tatami.config.ColumnFamilyKeys.BLOCK_USERS_CF;
/**
* Created by matthieudelafourniere on 7/7/16.
*/
@Repository
public class CassandraBlockRepository implements BlockRepository {
private ColumnFamilyTemplate blockedUsersTemplate;
@Inject
private Keyspace keyspaceOperator;
@PostConstruct
public void init() {
blockedUsersTemplate = new ThriftColumnFamilyTemplate(keyspaceOperator,
BLOCK_USERS_CF,
StringSerializer.get(),
StringSerializer.get());
blockedUsersTemplate.setCount(Constants.CASSANDRA_MAX_COLUMNS);
}
@Override
public void blockUser(String currentUserLogin, String blockedUserLogin) {
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.insert(currentUserLogin, BLOCK_USERS_CF, HFactory.createColumn(blockedUserLogin,
Calendar.getInstance().getTimeInMillis(), StringSerializer.get(), LongSerializer.get()));
}
@Override
public void unblockUser(String currentUserLogin, String unblockedUserLogin) {
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.delete(currentUserLogin, BLOCK_USERS_CF, unblockedUserLogin, StringSerializer.get());
}
@Override
public Collection getUsersBlockedBy(String userLogin) {
ColumnFamilyResult result = blockedUsersTemplate.queryColumns(userLogin);
Collection blockedUsers = new ArrayList();
for (String columnName : result.getColumnNames()) {
blockedUsers.add(columnName);
}
return blockedUsers;
}
@Override
public boolean isBlocked(String blockingLogin, String blockedLogin) {
Collection blockedEmails = getUsersBlockedBy(blockingLogin);
return blockedEmails.contains(blockedLogin);
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/cassandra/CassandraCounterRepository.java
================================================
package fr.ippon.tatami.repository.cassandra;
import fr.ippon.tatami.repository.CounterRepository;
import me.prettyprint.cassandra.model.thrift.ThriftCounterColumnQuery;
import me.prettyprint.cassandra.serializers.StringSerializer;
import me.prettyprint.hector.api.Keyspace;
import me.prettyprint.hector.api.beans.HCounterColumn;
import me.prettyprint.hector.api.factory.HFactory;
import me.prettyprint.hector.api.mutation.Mutator;
import me.prettyprint.hector.api.query.CounterQuery;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Repository;
import fr.ippon.tatami.config.ColumnFamilyKeys;
import javax.inject.Inject;
import static me.prettyprint.hector.api.factory.HFactory.createCounterColumn;
/**
* Cassandra implementation of the Counter repository.
*
* Structure :
* - Key = login
* - Name = counterId
* - Value = count
*
* @author Julien Dubois
*/
@Repository
public class CassandraCounterRepository implements CounterRepository {
private static final String STATUS_COUNTER = "STATUS_COUNTER";
private static final String FOLLOWERS_COUNTER = "FOLLOWERS_COUNTER";
private static final String FRIENDS_COUNTER = "FRIENDS_COUNTER";
@Inject
private Keyspace keyspaceOperator;
@Override
@CacheEvict(value = "user-cache", key = "#login")
public void incrementFollowersCounter(String login) {
incrementCounter(FOLLOWERS_COUNTER, login);
}
@Override
@CacheEvict(value = {"user-cache", "suggest-users-cache"}, key = "#login")
public void incrementFriendsCounter(String login) {
incrementCounter(FRIENDS_COUNTER, login);
}
@Override
@CacheEvict(value = "user-cache", key = "#login")
public void incrementStatusCounter(String login) {
incrementCounter(STATUS_COUNTER, login);
}
@Override
@CacheEvict(value = "user-cache", key = "#login")
public void decrementFollowersCounter(String login) {
decrementCounter(FOLLOWERS_COUNTER, login);
}
@Override
@CacheEvict(value = "user-cache", key = "#login")
public void decrementFriendsCounter(String login) {
decrementCounter(FRIENDS_COUNTER, login);
}
@Override
@CacheEvict(value = "user-cache", key = "#login")
public void decrementStatusCounter(String login) {
decrementCounter(STATUS_COUNTER, login);
}
@Override
public long getFollowersCounter(String login) {
return getCounter(FOLLOWERS_COUNTER, login);
}
@Override
public long getFriendsCounter(String login) {
return getCounter(FRIENDS_COUNTER, login);
}
@Override
public long getStatusCounter(String login) {
return getCounter(STATUS_COUNTER, login);
}
@Override
public void createFollowersCounter(String login) {
createCounter(FOLLOWERS_COUNTER, login);
}
@Override
public void createFriendsCounter(String login) {
createCounter(FRIENDS_COUNTER, login);
}
@Override
public void createStatusCounter(String login) {
createCounter(STATUS_COUNTER, login);
}
@Override
public void deleteCounters(String login) {
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.addCounterDeletion(login, ColumnFamilyKeys.COUNTER_CF, STATUS_COUNTER, StringSerializer.get());
mutator.addCounterDeletion(login, ColumnFamilyKeys.COUNTER_CF, FOLLOWERS_COUNTER, StringSerializer.get());
mutator.addCounterDeletion(login, ColumnFamilyKeys.COUNTER_CF, FRIENDS_COUNTER, StringSerializer.get());
mutator.execute();
}
private void createCounter(String counterName, String login) {
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.insertCounter(login, ColumnFamilyKeys.COUNTER_CF,
createCounterColumn(counterName, 0));
}
private void incrementCounter(String counterName, String login) {
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.incrementCounter(login, ColumnFamilyKeys.COUNTER_CF, counterName, 1);
}
private void decrementCounter(String counterName, String login) {
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.decrementCounter(login, ColumnFamilyKeys.COUNTER_CF, counterName, 1);
}
private long getCounter(String counterName, String login) {
CounterQuery counter =
new ThriftCounterColumnQuery(keyspaceOperator,
StringSerializer.get(),
StringSerializer.get());
counter.setColumnFamily(ColumnFamilyKeys.COUNTER_CF).setKey(login).setName(counterName);
HCounterColumn counterColumn = counter.execute().get();
if (counterColumn == null) {
return 0;
} else {
return counterColumn.getValue();
}
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/cassandra/CassandraDaylineRepository.java
================================================
package fr.ippon.tatami.repository.cassandra;
import fr.ippon.tatami.domain.UserStatusStat;
import fr.ippon.tatami.domain.status.Status;
import fr.ippon.tatami.repository.DaylineRepository;
import me.prettyprint.cassandra.serializers.StringSerializer;
import me.prettyprint.hector.api.Keyspace;
import me.prettyprint.hector.api.beans.CounterSlice;
import me.prettyprint.hector.api.beans.HCounterColumn;
import me.prettyprint.hector.api.factory.HFactory;
import me.prettyprint.hector.api.mutation.Mutator;
import me.prettyprint.hector.api.query.SliceCounterQuery;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Repository;
import javax.inject.Inject;
import java.util.Collection;
import java.util.TreeSet;
import static fr.ippon.tatami.config.ColumnFamilyKeys.DAYLINE_CF;
import static me.prettyprint.hector.api.factory.HFactory.createCounterSliceQuery;
/**
* Cassandra implementation of the user repository.
*
* Structure :
* - Key = day + domain
* - Name = username
* - Value = count
*
* @author Julien Dubois
*/
@Repository
public class CassandraDaylineRepository implements DaylineRepository {
@Inject
private Keyspace keyspaceOperator;
@Override
public void addStatusToDayline(Status status, String day) {
String key = getKey(status.getDomain(), day);
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.incrementCounter(key, DAYLINE_CF, status.getUsername(), 1);
}
@Override
@Cacheable("dayline-cache")
public Collection getDayline(String domain, String day) {
String key = getKey(domain, day);
Collection results = new TreeSet();
SliceCounterQuery query = createCounterSliceQuery(keyspaceOperator,
StringSerializer.get(), StringSerializer.get())
.setColumnFamily(DAYLINE_CF)
.setRange(null, null, false, Integer.MAX_VALUE)
.setKey(key);
CounterSlice queryResult = query.execute().get();
for (HCounterColumn column : queryResult.getColumns()) {
UserStatusStat stat = new UserStatusStat(column.getName(), column.getValue());
results.add(stat);
}
return results;
}
/**
* Generates the key for this column family.
*/
private String getKey(String domain, String day) {
return day + "-" + domain;
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/cassandra/CassandraDiscussionRepository.java
================================================
package fr.ippon.tatami.repository.cassandra;
import fr.ippon.tatami.repository.DiscussionRepository;
import me.prettyprint.cassandra.serializers.LongSerializer;
import me.prettyprint.cassandra.serializers.StringSerializer;
import me.prettyprint.hector.api.Keyspace;
import me.prettyprint.hector.api.beans.ColumnSlice;
import me.prettyprint.hector.api.beans.HColumn;
import me.prettyprint.hector.api.factory.HFactory;
import me.prettyprint.hector.api.mutation.Mutator;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Repository;
import javax.inject.Inject;
import java.util.Calendar;
import java.util.Collection;
import java.util.LinkedHashSet;
import static fr.ippon.tatami.config.ColumnFamilyKeys.DISCUSSION_CF;
import static me.prettyprint.hector.api.factory.HFactory.createSliceQuery;
/**
* Cassandra implementation of the StatusDetails repository.
*
* Structure :
* - Key = originial status Id
* - Name = time
* - Value = reply status Id
*
* @author Julien Dubois
*/
@Repository
public class CassandraDiscussionRepository implements DiscussionRepository {
@Inject
private Keyspace keyspaceOperator;
@Override
@CacheEvict(value = "status-cache", key = "#originalStatusId")
public void addReplyToDiscussion(String originalStatusId, String replyStatusId) {
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.insert(originalStatusId, DISCUSSION_CF,
HFactory.createColumn(
Calendar.getInstance().getTimeInMillis(),
replyStatusId,
LongSerializer.get(),
StringSerializer.get()));
}
@Override
public Collection findStatusIdsInDiscussion(String originalStatusId) {
ColumnSlice result = createSliceQuery(keyspaceOperator,
StringSerializer.get(), LongSerializer.get(), StringSerializer.get())
.setColumnFamily(DISCUSSION_CF)
.setKey(originalStatusId)
.setRange(null, null, false, Integer.MAX_VALUE)
.execute()
.get();
Collection statusIds = new LinkedHashSet();
for (HColumn column : result.getColumns()) {
statusIds.add(column.getValue());
}
return statusIds;
}
@Override
public boolean hasReply(String statusId) {
int zeroOrOne = HFactory.createCountQuery(keyspaceOperator, StringSerializer.get(), LongSerializer.get())
.setColumnFamily(DISCUSSION_CF)
.setKey(statusId)
.setRange(null, null, 1)
.execute()
.get();
return zeroOrOne > 0;
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/cassandra/CassandraDomainConfigurationRepository.java
================================================
package fr.ippon.tatami.repository.cassandra;
import javax.inject.Inject;
import me.prettyprint.hom.EntityManagerImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Repository;
import fr.ippon.tatami.domain.DomainConfiguration;
import fr.ippon.tatami.repository.DomainConfigurationRepository;
/**
* Cassandra implementation of the DomainConfiguration repository.
*
* @author Julien Dubois
*/
@Repository
public class CassandraDomainConfigurationRepository implements DomainConfigurationRepository {
private final Logger log = LoggerFactory.getLogger(CassandraDomainConfigurationRepository.class);
@Inject
private EntityManagerImpl em;
@Override
public void updateDomainConfiguration(DomainConfiguration domainConfiguration) {
setDefaultValues(domainConfiguration);
em.persist(domainConfiguration);
}
@Override
public DomainConfiguration findDomainConfigurationByDomain(String domain) {
DomainConfiguration domainConfiguration;
try {
domainConfiguration = em.find(DomainConfiguration.class, domain);
} catch (Exception e) {
log.debug("Exception while looking for domain {} : {}", domain, e.toString());
return null;
}
if (domainConfiguration == null) {
domainConfiguration = new DomainConfiguration();
domainConfiguration.setDomain(domain);
setDefaultValues(domainConfiguration);
em.persist(domainConfiguration);
}
if (domain.equals("ippon.fr")) {
domainConfiguration.setSubscriptionLevel(DomainConfiguration.SubscriptionAndStorageSizeOptions.IPPONSUSCRIPTION);
domainConfiguration.setStorageSize(DomainConfiguration.SubscriptionAndStorageSizeOptions.IPPONSIZE);
}
return domainConfiguration;
}
private void setDefaultValues(DomainConfiguration domainConfiguration) {
if (domainConfiguration.getStorageSize() == null) {
domainConfiguration.setStorageSize(DomainConfiguration.SubscriptionAndStorageSizeOptions.BASICSIZE);
}
if (domainConfiguration.getSubscriptionLevel() == null) {
domainConfiguration.setSubscriptionLevel(DomainConfiguration.SubscriptionAndStorageSizeOptions.BASICSUSCRIPTION);
}
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/cassandra/CassandraDomainRepository.java
================================================
package fr.ippon.tatami.repository.cassandra;
import fr.ippon.tatami.config.Constants;
import fr.ippon.tatami.domain.Domain;
import fr.ippon.tatami.repository.DomainRepository;
import me.prettyprint.cassandra.serializers.LongSerializer;
import me.prettyprint.cassandra.serializers.StringSerializer;
import me.prettyprint.hector.api.Keyspace;
import me.prettyprint.hector.api.beans.ColumnSlice;
import me.prettyprint.hector.api.beans.HColumn;
import me.prettyprint.hector.api.beans.OrderedRows;
import me.prettyprint.hector.api.beans.Row;
import me.prettyprint.hector.api.factory.HFactory;
import me.prettyprint.hector.api.mutation.Mutator;
import me.prettyprint.hector.api.query.QueryResult;
import me.prettyprint.hector.api.query.RangeSlicesQuery;
import org.springframework.stereotype.Repository;
import javax.inject.Inject;
import java.util.*;
import static fr.ippon.tatami.config.ColumnFamilyKeys.DOMAIN_CF;
import static me.prettyprint.hector.api.factory.HFactory.createRangeSlicesQuery;
import static me.prettyprint.hector.api.factory.HFactory.createSliceQuery;
/**
* Cassandra implementation of the Domain repository.
*
* Structure :
* - Key = domain
* - Name = login
* - Value = time
*
* @author Julien Dubois
*/
@Repository
public class CassandraDomainRepository implements DomainRepository {
@Inject
private Keyspace keyspaceOperator;
@Override
public void addUserInDomain(String domain, String login) {
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.insert(domain, DOMAIN_CF, HFactory.createColumn(login,
Calendar.getInstance().getTimeInMillis(), StringSerializer.get(), LongSerializer.get()));
}
@Override
public void updateUserInDomain(String domain, String login) {
this.addUserInDomain(domain, login);
}
@Override
public void deleteUserInDomain(String domain, String login) {
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.delete(domain, DOMAIN_CF, login, StringSerializer.get());
}
@Override
public List getLoginsInDomain(String domain, int pagination) {
int maxColumns = pagination + Constants.PAGINATION_SIZE;
List logins = new ArrayList();
ColumnSlice result = createSliceQuery(keyspaceOperator,
StringSerializer.get(), StringSerializer.get(), StringSerializer.get())
.setColumnFamily(DOMAIN_CF)
.setKey(domain)
.setRange(null, null, false, maxColumns)
.execute()
.get();
int index = 0;
for (HColumn column : result.getColumns()) {
// We take one more item, to display (or not) the "next" button if there is an item after the displayed list.
if (index > maxColumns) {
break;
}
if (index >= pagination) {
logins.add(column.getName());
}
index++;
}
return logins;
}
@Override
public List getLoginsInDomain(String domain) {
List logins = new ArrayList();
ColumnSlice result = createSliceQuery(keyspaceOperator,
StringSerializer.get(), StringSerializer.get(), StringSerializer.get())
.setColumnFamily(DOMAIN_CF)
.setKey(domain)
.setRange(null, null, false, Integer.MAX_VALUE)
.execute()
.get();
for (HColumn column : result.getColumns()) {
logins.add(column.getName());
}
return logins;
}
@Override
public Set getAllDomains() {
Set domains = new HashSet();
RangeSlicesQuery query = createRangeSlicesQuery(keyspaceOperator,
StringSerializer.get(), StringSerializer.get(), StringSerializer.get())
.setColumnFamily(DOMAIN_CF)
.setRange(null, null, false, Integer.MAX_VALUE)
.setRowCount(Constants.CASSANDRA_MAX_ROWS);
QueryResult> result = query.execute();
List> rows = result.get().getList();
for (Row row : rows) {
Domain domain = new Domain();
domain.setName(row.getKey());
domain.setNumberOfUsers(row.getColumnSlice().getColumns().size());
domains.add(domain);
}
return domains;
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/cassandra/CassandraDomainlineRepository.java
================================================
package fr.ippon.tatami.repository.cassandra;
import fr.ippon.tatami.repository.DomainlineRepository;
import me.prettyprint.hector.api.Keyspace;
import org.springframework.stereotype.Repository;
import javax.inject.Inject;
import java.util.Collection;
import java.util.List;
import static fr.ippon.tatami.config.ColumnFamilyKeys.DOMAINLINE_CF;
/**
* Cassandra implementation of the Domain line repository.
*
* Structure :
* - Key = domain
* - Name = statusId
* - Value = ""
*
* @author Julien Dubois
*/
@Repository
public class CassandraDomainlineRepository extends AbstractCassandraLineRepository implements DomainlineRepository {
private final static int COLUMN_TTL = 60 * 60 * 24 * 30; // The column is stored for 30 days.
@Inject
private Keyspace keyspaceOperator;
@Override
public void addStatusToDomainline(String domain, String statusId) {
addStatus(domain, DOMAINLINE_CF, statusId, COLUMN_TTL);
}
@Override
public void removeStatusFromDomainline(String domain, Collection statusIdsToDelete) {
removeStatuses(domain, DOMAINLINE_CF, statusIdsToDelete);
}
@Override
public List getDomainline(String domain, int size, String start, String finish) {
return getLineFromCF(DOMAINLINE_CF, domain, size, start, finish);
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/cassandra/CassandraFavoritelineRepository.java
================================================
package fr.ippon.tatami.repository.cassandra;
import fr.ippon.tatami.repository.FavoritelineRepository;
import me.prettyprint.cassandra.serializers.StringSerializer;
import me.prettyprint.cassandra.serializers.UUIDSerializer;
import me.prettyprint.hector.api.Keyspace;
import me.prettyprint.hector.api.beans.ColumnSlice;
import me.prettyprint.hector.api.beans.HColumn;
import me.prettyprint.hector.api.factory.HFactory;
import me.prettyprint.hector.api.mutation.Mutator;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Repository;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import static fr.ippon.tatami.config.ColumnFamilyKeys.FAVLINE_CF;
import static me.prettyprint.hector.api.factory.HFactory.createSliceQuery;
/**
* Cassandra implementation of the favoriteline repository.
*
* Structure :
* - Key = login
* - Name = statusId
* - Value = ""
*
* @author Julien Dubois
*/
@Repository
public class CassandraFavoritelineRepository implements FavoritelineRepository {
@Inject
private Keyspace keyspaceOperator;
@Override
@CacheEvict(value = "favorites-cache", key = "#login")
public void addStatusToFavoriteline(String login, String statusId) {
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.insert(login, FAVLINE_CF, HFactory.createColumn(UUID.fromString(statusId), "",
UUIDSerializer.get(), StringSerializer.get()));
}
@Override
@CacheEvict(value = "favorites-cache", key = "#login")
public void removeStatusFromFavoriteline(String login, String statusId) {
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.delete(login, FAVLINE_CF, UUID.fromString(statusId), UUIDSerializer.get());
}
@Override
@Cacheable("favorites-cache")
public List getFavoriteline(String login) {
List line = new ArrayList();
ColumnSlice result = createSliceQuery(keyspaceOperator,
StringSerializer.get(), UUIDSerializer.get(), StringSerializer.get())
.setColumnFamily(FAVLINE_CF)
.setKey(login)
.setRange(null, null, true, 50)
.execute()
.get();
for (HColumn column : result.getColumns()) {
line.add(column.getName().toString());
}
return line;
}
@Override
public void deleteFavoriteline(String login) {
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.addDeletion(login, FAVLINE_CF);
mutator.execute();
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/cassandra/CassandraFollowerRepository.java
================================================
package fr.ippon.tatami.repository.cassandra;
import fr.ippon.tatami.repository.FollowerRepository;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Repository;
import fr.ippon.tatami.config.ColumnFamilyKeys;
import java.util.Collection;
/**
* Cassandra implementation of the Follower repository.
*
* Structure :
* - Key = login
* - Name = follower login
* - Value = time
*
* @author Julien Dubois
*/
@Repository
public class CassandraFollowerRepository extends AbstractCassandraFollowerRepository implements FollowerRepository {
@Override
@CacheEvict(value = "followers-cache", key = "#login")
public void addFollower(String login, String followerLogin) {
super.addFollower(login, followerLogin);
}
@Override
@CacheEvict(value = "followers-cache", key = "#login")
public void removeFollower(String login, String followerLogin) {
super.removeFollower(login, followerLogin);
}
@Override
@Cacheable("followers-cache")
public Collection findFollowersForUser(String login) {
return super.findFollowers(login);
}
@Override
public String getFollowersCF() {
return ColumnFamilyKeys.FOLLOWERS_CF;
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/cassandra/CassandraFriendRepository.java
================================================
package fr.ippon.tatami.repository.cassandra;
import fr.ippon.tatami.repository.FriendRepository;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Repository;
import java.util.List;
import static fr.ippon.tatami.config.ColumnFamilyKeys.FRIENDS_CF;
/**
* Cassandra implementation of the Friend repository.
*
* Structure :
* - Key = login
* - Name = friend login
* - Value = time
*
* @author Julien Dubois
*/
@Repository
public class CassandraFriendRepository extends AbstractCassandraFriendRepository implements FriendRepository {
@Override
@CacheEvict(value = "friends-cache", key = "#login")
public void addFriend(String login, String friendLogin) {
super.addFriend(login, friendLogin);
}
@Override
@CacheEvict(value = "friends-cache", key = "#login")
public void removeFriend(String login, String friendLogin) {
super.removeFriend(login, friendLogin);
}
@Override
@Cacheable("friends-cache")
public List findFriendsForUser(String login) {
return super.findFriends(login);
}
@Override
public String getFriendsCF() {
return FRIENDS_CF;
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/cassandra/CassandraGroupCounterRepository.java
================================================
package fr.ippon.tatami.repository.cassandra;
import fr.ippon.tatami.repository.GroupCounterRepository;
import me.prettyprint.cassandra.model.thrift.ThriftCounterColumnQuery;
import me.prettyprint.cassandra.serializers.StringSerializer;
import me.prettyprint.hector.api.Keyspace;
import me.prettyprint.hector.api.factory.HFactory;
import me.prettyprint.hector.api.mutation.Mutator;
import me.prettyprint.hector.api.query.CounterQuery;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Repository;
import javax.inject.Inject;
import static fr.ippon.tatami.config.ColumnFamilyKeys.GROUP_COUNTER_CF;
/**
* Cassandra implementation of the Group Counter repository.
*
* Structure :
* - Key = domain
* - Name = groupId
* - Value = count
*
* @author Julien Dubois
*/
@Repository
public class CassandraGroupCounterRepository implements GroupCounterRepository {
@Inject
private Keyspace keyspaceOperator;
@Override
public long getGroupCounter(String domain, String groupId) {
CounterQuery counter =
new ThriftCounterColumnQuery(keyspaceOperator,
StringSerializer.get(),
StringSerializer.get());
counter.setColumnFamily(GROUP_COUNTER_CF).setKey(domain).setName(groupId);
return counter.execute().get().getValue();
}
protected final Logger log = LoggerFactory.getLogger(this.getClass().getCanonicalName());
@Override
public void incrementGroupCounter(String domain, String groupId) {
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.incrementCounter(domain, GROUP_COUNTER_CF, groupId, 1);
}
@Override
public void decrementGroupCounter(String domain, String groupId) {
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.decrementCounter(domain, GROUP_COUNTER_CF, groupId, 1);
}
@Override
public void deleteGroupCounter(String domain, String groupId) {
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.addCounterDeletion(domain, GROUP_COUNTER_CF, groupId, StringSerializer.get());
mutator.execute();
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/cassandra/CassandraGroupDetailsRepository.java
================================================
package fr.ippon.tatami.repository.cassandra;
import fr.ippon.tatami.domain.Group;
import fr.ippon.tatami.repository.GroupDetailsRepository;
import me.prettyprint.cassandra.serializers.StringSerializer;
import me.prettyprint.hector.api.Keyspace;
import me.prettyprint.hector.api.beans.ColumnSlice;
import me.prettyprint.hector.api.beans.HColumn;
import me.prettyprint.hector.api.factory.HFactory;
import me.prettyprint.hector.api.mutation.Mutator;
import org.springframework.stereotype.Repository;
import javax.inject.Inject;
import static fr.ippon.tatami.config.ColumnFamilyKeys.GROUP_DETAILS_CF;
import static me.prettyprint.hector.api.factory.HFactory.createSliceQuery;
/**
* Cassandra implementation of the Group Details repository.
*
* Structure :
* - Key = Group ID
* - Name / Value pairs of group details
*
* @author Julien Dubois
*/
@Repository
public class CassandraGroupDetailsRepository implements GroupDetailsRepository {
private static final String NAME = "name";
private static final String DESCRIPTION = "description";
private static final String PUBLIC_GROUP = "publicGroup";
private static final String ARCHIVED_GROUP = "archivedGroup";
@Inject
private Keyspace keyspaceOperator;
@Override
public void createGroupDetails(String groupId, String name, String description, boolean publicGroup) {
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.insert(groupId, GROUP_DETAILS_CF, HFactory.createColumn(NAME,
name, StringSerializer.get(), StringSerializer.get()));
mutator.insert(groupId, GROUP_DETAILS_CF, HFactory.createColumn(DESCRIPTION,
description, StringSerializer.get(), StringSerializer.get()));
mutator.insert(groupId, GROUP_DETAILS_CF, HFactory.createColumn(PUBLIC_GROUP,
(Boolean.valueOf(publicGroup)).toString(), StringSerializer.get(), StringSerializer.get()));
mutator.insert(groupId, GROUP_DETAILS_CF, HFactory.createColumn(ARCHIVED_GROUP,
Boolean.FALSE.toString(), StringSerializer.get(), StringSerializer.get()));
}
@Override
public void editGroupDetails(String groupId, String name, String description, boolean archivedGroup) {
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.insert(groupId, GROUP_DETAILS_CF, HFactory.createColumn(NAME,
name, StringSerializer.get(), StringSerializer.get()));
mutator.insert(groupId, GROUP_DETAILS_CF, HFactory.createColumn(DESCRIPTION,
description, StringSerializer.get(), StringSerializer.get()));
mutator.insert(groupId, GROUP_DETAILS_CF, HFactory.createColumn(ARCHIVED_GROUP,
(Boolean.valueOf(archivedGroup)).toString(), StringSerializer.get(), StringSerializer.get()));
}
@Override
public Group getGroupDetails(String groupId) {
Group group = new Group();
group.setGroupId(groupId);
group.setPublicGroup(false);
ColumnSlice result = createSliceQuery(keyspaceOperator,
StringSerializer.get(), StringSerializer.get(), StringSerializer.get())
.setColumnFamily(GROUP_DETAILS_CF)
.setKey(groupId)
.setRange(null, null, false, 4)
.execute()
.get();
for (HColumn column : result.getColumns()) {
if (column.getName().equals(NAME)) {
group.setName(column.getValue());
} else if (column.getName().equals(DESCRIPTION)) {
group.setDescription(column.getValue());
} else if (column.getName().equals(PUBLIC_GROUP)) {
if (column.getValue().equals(Boolean.TRUE.toString())) {
group.setPublicGroup(true);
}
} else if (column.getName().equals(ARCHIVED_GROUP)) {
if (column.getValue().equals(Boolean.TRUE.toString())) {
group.setArchivedGroup(true);
}
}
}
return group;
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/cassandra/CassandraGroupMembersRepository.java
================================================
package fr.ippon.tatami.repository.cassandra;
import fr.ippon.tatami.config.GroupRoles;
import fr.ippon.tatami.repository.GroupMembersRepository;
import me.prettyprint.cassandra.serializers.StringSerializer;
import me.prettyprint.hector.api.Keyspace;
import me.prettyprint.hector.api.beans.ColumnSlice;
import me.prettyprint.hector.api.beans.HColumn;
import me.prettyprint.hector.api.factory.HFactory;
import me.prettyprint.hector.api.mutation.Mutator;
import org.springframework.stereotype.Repository;
import javax.inject.Inject;
import java.util.HashMap;
import java.util.Map;
import static fr.ippon.tatami.config.ColumnFamilyKeys.GROUP_MEMBERS_CF;
import static me.prettyprint.hector.api.factory.HFactory.createSliceQuery;
/**
* Cassandra implementation of the Group members repository.
*
* Structure :
* - Key = group ID
* - Name = login
* - Value = role
*
* @author Julien Dubois
*/
@Repository
public class CassandraGroupMembersRepository implements GroupMembersRepository {
@Inject
private Keyspace keyspaceOperator;
@Override
public void addMember(String groupId, String login) {
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.insert(groupId, GROUP_MEMBERS_CF, HFactory.createColumn(login,
GroupRoles.MEMBER, StringSerializer.get(), StringSerializer.get()));
}
@Override
public void addAdmin(String groupId, String login) {
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.insert(groupId, GROUP_MEMBERS_CF, HFactory.createColumn(login,
GroupRoles.ADMIN, StringSerializer.get(), StringSerializer.get()));
}
@Override
public void removeMember(String groupId, String login) {
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.delete(groupId, GROUP_MEMBERS_CF, login, StringSerializer.get());
}
@Override
public Map findMembers(String groupId) {
Map members = new HashMap();
ColumnSlice result = createSliceQuery(keyspaceOperator,
StringSerializer.get(), StringSerializer.get(), StringSerializer.get())
.setColumnFamily(GROUP_MEMBERS_CF)
.setKey(groupId)
.setRange(null, null, false, Integer.MAX_VALUE)
.execute()
.get();
for (HColumn column : result.getColumns()) {
members.put(column.getName(), column.getValue());
}
return members;
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/cassandra/CassandraGroupRepository.java
================================================
package fr.ippon.tatami.repository.cassandra;
import fr.ippon.tatami.domain.Group;
import fr.ippon.tatami.repository.GroupRepository;
import me.prettyprint.cassandra.serializers.StringSerializer;
import me.prettyprint.cassandra.utils.TimeUUIDUtils;
import me.prettyprint.hector.api.Keyspace;
import me.prettyprint.hector.api.beans.HColumn;
import me.prettyprint.hector.api.factory.HFactory;
import me.prettyprint.hector.api.mutation.Mutator;
import me.prettyprint.hector.api.query.ColumnQuery;
import org.springframework.stereotype.Repository;
import javax.inject.Inject;
import static fr.ippon.tatami.config.ColumnFamilyKeys.GROUP_CF;
/**
* Cassandra implementation of the Group repository.
*
* Structure :
* - Key = domain
* - Name = Group ID
* - Value = ""
*
* @author Julien Dubois
*/
@Repository
public class CassandraGroupRepository implements GroupRepository {
@Inject
private Keyspace keyspaceOperator;
@Override
public String createGroup(String domain) {
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
String groupId = TimeUUIDUtils.getUniqueTimeUUIDinMillis().toString();
mutator.insert(domain, GROUP_CF, HFactory.createColumn(groupId,
"", StringSerializer.get(), StringSerializer.get()));
return groupId;
}
@Override
public Group getGroupById(String domain, String groupId) {
ColumnQuery query = HFactory.createStringColumnQuery(keyspaceOperator);
HColumn column =
query.setColumnFamily(GROUP_CF)
.setKey(domain)
.setName(groupId)
.execute()
.get();
if (column != null) {
Group group = new Group();
group.setDomain(domain);
group.setGroupId(groupId);
return group;
} else {
return null;
}
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/cassandra/CassandraGrouplineRepository.java
================================================
package fr.ippon.tatami.repository.cassandra;
import fr.ippon.tatami.repository.GrouplineRepository;
import me.prettyprint.hector.api.Keyspace;
import org.springframework.stereotype.Repository;
import fr.ippon.tatami.config.ColumnFamilyKeys;
import javax.inject.Inject;
import java.util.Collection;
import java.util.List;
/**
* Cassandra implementation of the Group line repository.
*
* Structure :
* - Key = groupId
* - Name = statusId
* - Value = ""
*
* @author Julien Dubois
*/
@Repository
public class CassandraGrouplineRepository extends AbstractCassandraLineRepository implements GrouplineRepository {
@Inject
private Keyspace keyspaceOperator;
@Override
public void addStatusToGroupline(String groupId, String statusId) {
addStatus(groupId, ColumnFamilyKeys.GROUPLINE_CF, statusId);
}
@Override
public void removeStatusesFromGroupline(String groupId, Collection statusIdsToDelete) {
removeStatuses(groupId, ColumnFamilyKeys.GROUPLINE_CF, statusIdsToDelete);
}
@Override
public List getGroupline(String groupId, int size, String start, String finish) {
return getLineFromCF(ColumnFamilyKeys.GROUPLINE_CF, groupId, size, start, finish);
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/cassandra/CassandraIdempotentRepository.java
================================================
package fr.ippon.tatami.repository.cassandra;
import me.prettyprint.cassandra.serializers.StringSerializer;
import me.prettyprint.hector.api.Keyspace;
import me.prettyprint.hector.api.beans.HColumn;
import me.prettyprint.hector.api.factory.HFactory;
import me.prettyprint.hector.api.mutation.Mutator;
import me.prettyprint.hector.api.query.ColumnQuery;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import fr.ippon.tatami.repository.IdempotentRepository;
import javax.inject.Inject;
/**
* Used to de-deplucate Camel messages.
*/
@Component
public class CassandraIdempotentRepository implements IdempotentRepository {
private final Logger log = LoggerFactory.getLogger(CassandraIdempotentRepository.class);
private final static String KEY = "Default";
private final static String TATAMIBOT_DUPLICATE_CF = "TatamiBotDuplicate";
private final static int COLUMN_TTL = 60 * 60 * 24 * 30; // The column is stored for 30 days.
@Inject
private Keyspace keyspaceOperator;
@Override
public boolean add(String key) {
if (contains(key)) {
log.debug("Duplicate message detected!");
return false;
} else {
log.debug("Adding new message to the idempotent repository");
HColumn column =
HFactory.createColumn(
key,
"",
COLUMN_TTL,
StringSerializer.get(),
StringSerializer.get());
Mutator mutator =
HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.insert(KEY, TATAMIBOT_DUPLICATE_CF, column);
return true;
}
}
@Override
public boolean contains(String key) {
log.debug("Test message duplication with key : {}", key);
ColumnQuery query = HFactory.createStringColumnQuery(keyspaceOperator);
HColumn column =
query.setColumnFamily(TATAMIBOT_DUPLICATE_CF)
.setKey(KEY)
.setName(key)
.execute()
.get();
return column != null;
}
@Override
public boolean remove(String key) {
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.delete(KEY, TATAMIBOT_DUPLICATE_CF, key, StringSerializer.get());
return true;
}
@Override
public boolean confirm(String key) {
return true; // noop
}
@Override
public void start() throws Exception {
}
@Override
public void stop() throws Exception {
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/cassandra/CassandraMailDigestRepository.java
================================================
package fr.ippon.tatami.repository.cassandra;
import fr.ippon.tatami.config.Constants;
import fr.ippon.tatami.domain.DigestType;
import fr.ippon.tatami.repository.MailDigestRepository;
import me.prettyprint.cassandra.serializers.LongSerializer;
import me.prettyprint.cassandra.serializers.StringSerializer;
import me.prettyprint.hector.api.Keyspace;
import me.prettyprint.hector.api.beans.ColumnSlice;
import me.prettyprint.hector.api.beans.HColumn;
import me.prettyprint.hector.api.factory.HFactory;
import me.prettyprint.hector.api.mutation.Mutator;
import org.springframework.stereotype.Repository;
import fr.ippon.tatami.config.ColumnFamilyKeys;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import static me.prettyprint.hector.api.factory.HFactory.createSliceQuery;
/**
* MailDigestRepository implementation for cassandra
*
* Structure :
* - Key = digestType_[day]_domain
* - Name = login
* - Value = time
*
* Note : in the key, the [day] part is only used for weekly digest and
* represents the day the user subscribed to the digest.
*
* @author Pierre Rust
*/
@Repository
public class CassandraMailDigestRepository implements MailDigestRepository {
@Inject
private Keyspace keyspaceOperator;
@Override
public void subscribeToDigest(DigestType digestType, String login, String domain, String day) {
Calendar cal = Calendar.getInstance();
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.insert(buildKey(digestType, domain, day), ColumnFamilyKeys.MAILDIGEST_CF,
HFactory.createColumn(login, cal.getTimeInMillis(), StringSerializer.get(), LongSerializer.get()));
}
@Override
public void unsubscribeFromDigest(DigestType digestType, String login, String domain, String day) {
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.delete(buildKey(digestType, domain, day), ColumnFamilyKeys.MAILDIGEST_CF, login, StringSerializer.get());
}
@Override
public List getLoginsRegisteredToDigest(DigestType digestType, String domain,
String day, int pagination) {
List logins = new ArrayList();
ColumnSlice result = createSliceQuery(keyspaceOperator,
StringSerializer.get(), StringSerializer.get(), StringSerializer.get())
.setColumnFamily(ColumnFamilyKeys.MAILDIGEST_CF)
.setKey(buildKey(digestType, domain, day))
.setRange(null, null, false, Integer.MAX_VALUE)
.execute()
.get();
int index = 0;
for (HColumn column : result.getColumns()) {
// We take one more item, to display (or not) the "next" button if there is an item after the displayed list.
if (index > pagination + Constants.PAGINATION_SIZE) {
break;
}
if (index >= pagination) {
logins.add(column.getName());
}
index++;
}
return logins;
}
/**
* @return the row key
*/
private String buildKey(DigestType digestType, String domain, String day) {
String key;
if (DigestType.WEEKLY_DIGEST == digestType) {
key = digestType.toString() + "_" + day + "_" + domain;
} else {
key = digestType + "_" + domain;
}
return key;
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/cassandra/CassandraMentionlineRepository.java
================================================
package fr.ippon.tatami.repository.cassandra;
import fr.ippon.tatami.repository.MentionlineRepository;
import org.springframework.stereotype.Repository;
import fr.ippon.tatami.config.ColumnFamilyKeys;
import java.util.Collection;
import java.util.List;
/**
* Cassandra implementation of the Userline repository.
*
* Structure :
* - Key : login
* - Name : status Id
* - Value : ""
*
* @author Julien Dubois
*/
@Repository
public class CassandraMentionlineRepository extends AbstractCassandraLineRepository implements MentionlineRepository {
@Override
public void addStatusToMentionline(String mentionedLogin, String statusId) {
addStatus(mentionedLogin, ColumnFamilyKeys.MENTIONLINE_CF, statusId);
}
@Override
public void removeStatusesFromMentionline(String mentionedLogin, Collection statusIdsToDelete) {
removeStatuses(mentionedLogin, ColumnFamilyKeys.MENTIONLINE_CF, statusIdsToDelete);
}
@Override
public List getMentionline(String login, int size, String start, String finish) {
return getLineFromCF(ColumnFamilyKeys.MENTIONLINE_CF, login, size, start, finish);
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/cassandra/CassandraRegistrationRepository.java
================================================
package fr.ippon.tatami.repository.cassandra;
import com.google.common.collect.Maps;
import fr.ippon.tatami.repository.RegistrationRepository;
import fr.ippon.tatami.service.util.RandomUtil;
import me.prettyprint.cassandra.serializers.StringSerializer;
import me.prettyprint.hector.api.Keyspace;
import me.prettyprint.hector.api.beans.ColumnSlice;
import me.prettyprint.hector.api.beans.HColumn;
import me.prettyprint.hector.api.factory.HFactory;
import me.prettyprint.hector.api.mutation.Mutator;
import me.prettyprint.hector.api.query.ColumnQuery;
import me.prettyprint.hector.api.query.SliceQuery;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Repository;
import javax.inject.Inject;
import java.util.List;
import java.util.Map;
import static fr.ippon.tatami.config.ColumnFamilyKeys.REGISTRATION_CF;
/**
* Cassandra implementation of the Registration repository.
*
* Structure :
* - Key = "registration_key"
* - Name = key
* - Value = login
*
* @author Julien Dubois
*/
@Repository
public class CassandraRegistrationRepository implements RegistrationRepository {
private static final Logger log = LoggerFactory.getLogger(CassandraRegistrationRepository.class);
private final static String ROW_KEY = "registration_key";
private final static int COLUMN_TTL = 60 * 60 * 24 * 2; // The column is stored for 2 days.
@Inject
private Keyspace keyspaceOperator;
@Override
public String generateRegistrationKey(String login) {
String key = RandomUtil.generateRegistrationKey();
HColumn column = HFactory.createColumn(key,
login, COLUMN_TTL, StringSerializer.get(), StringSerializer.get());
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.insert(ROW_KEY, REGISTRATION_CF, column);
return key;
}
@Override
public String getLoginByRegistrationKey(String registrationKey) {
ColumnQuery query = HFactory.createStringColumnQuery(keyspaceOperator);
HColumn column =
query.setColumnFamily(REGISTRATION_CF)
.setKey(ROW_KEY)
.setName(registrationKey)
.execute()
.get();
if (column != null) {
return column.getValue();
} else {
return null;
}
}
/**
* !! For testing purpose only !!
* This method is not efficient and is limited to 10000 registrations.
* Other limitation : if a login is associated to multiple registrationKey
*/
public Map _getAllRegistrationKeyByLogin() {
log.warn("Calling _getAllRegistrationKeyByLogin() is only for testing purposes!");
Map registrationKeyByLogin = Maps.newHashMap();
SliceQuery sliceQuery = HFactory.createSliceQuery(keyspaceOperator,
StringSerializer.get(), StringSerializer.get(), StringSerializer.get());
ColumnSlice columnSlice =
sliceQuery.setColumnFamily(REGISTRATION_CF)
.setKey(ROW_KEY)
.setRange(null, null, false, 10000)
.execute().get();
List> columns = columnSlice.getColumns();
for (HColumn hColumn : columns) {
// WARN : here we don't handle multiple registrationKey for one login
registrationKeyByLogin.put(hColumn.getValue(), hColumn.getName());
log.debug("Key={}|Value={}", hColumn.getValue(), hColumn.getName());
}
return registrationKeyByLogin;
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/cassandra/CassandraRssUidRepository.java
================================================
package fr.ippon.tatami.repository.cassandra;
import fr.ippon.tatami.repository.RssUidRepository;
import fr.ippon.tatami.service.util.RandomUtil;
import me.prettyprint.cassandra.serializers.StringSerializer;
import me.prettyprint.hector.api.Keyspace;
import me.prettyprint.hector.api.beans.HColumn;
import me.prettyprint.hector.api.factory.HFactory;
import me.prettyprint.hector.api.mutation.Mutator;
import me.prettyprint.hector.api.query.ColumnQuery;
import org.springframework.stereotype.Repository;
import javax.inject.Inject;
import static fr.ippon.tatami.config.ColumnFamilyKeys.RSS_CF;
/**
* Cassandra implementation of the RssUid repository.
*
* Structure : - Key = "rss_uid" - Name = key - Value = login
*
* @author Pierre Rust
*/
@Repository
public class CassandraRssUidRepository implements RssUidRepository {
private final static String ROW_KEY = "rss_uid";
@Inject
private Keyspace keyspaceOperator;
@Override
public String generateRssUid(String login) {
String key = RandomUtil.generateRegistrationKey();
HColumn column = HFactory.createColumn(key,
login, StringSerializer.get(), StringSerializer.get());
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.insert(ROW_KEY, RSS_CF, column);
return key;
}
@Override
public String getLoginByRssUid(String rssUid) {
ColumnQuery query = HFactory.createStringColumnQuery(keyspaceOperator);
HColumn column =
query.setColumnFamily(RSS_CF)
.setKey(ROW_KEY)
.setName(rssUid)
.execute()
.get();
if (column != null) {
return column.getValue();
} else {
return null;
}
}
@Override
public void removeRssUid(String rssUid) {
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.delete(ROW_KEY, RSS_CF, rssUid, StringSerializer.get());
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/cassandra/CassandraSharesRepository.java
================================================
package fr.ippon.tatami.repository.cassandra;
import fr.ippon.tatami.repository.SharesRepository;
import me.prettyprint.cassandra.serializers.LongSerializer;
import me.prettyprint.cassandra.serializers.StringSerializer;
import me.prettyprint.hector.api.Keyspace;
import me.prettyprint.hector.api.beans.ColumnSlice;
import me.prettyprint.hector.api.beans.HColumn;
import me.prettyprint.hector.api.factory.HFactory;
import me.prettyprint.hector.api.mutation.Mutator;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Repository;
import javax.inject.Inject;
import java.util.Calendar;
import java.util.Collection;
import java.util.LinkedHashSet;
import static fr.ippon.tatami.config.ColumnFamilyKeys.SHARES_CF;
import static me.prettyprint.hector.api.factory.HFactory.createSliceQuery;
/**
* Cassandra implementation of the Shares repository.
* Lists the shares for a given status.
*
* Structure :
* - Key = status Id
* - Name = time
* - Value = login who shared this status
*
* @author Julien Dubois
*/
@Repository
public class CassandraSharesRepository implements SharesRepository {
@Inject
private Keyspace keyspaceOperator;
@Override
@CacheEvict(value = "shared-cache", key = "#statusId")
public void newShareByLogin(String statusId, String sharedByLogin) {
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.insert(statusId, SHARES_CF,
HFactory.createColumn(
Calendar.getInstance().getTimeInMillis(),
sharedByLogin,
LongSerializer.get(),
StringSerializer.get()));
}
@Override
@Cacheable("shared-cache")
public Collection findLoginsWhoSharedAStatus(String statusId) {
ColumnSlice result = createSliceQuery(keyspaceOperator,
StringSerializer.get(), LongSerializer.get(), StringSerializer.get())
.setColumnFamily(SHARES_CF)
.setKey(statusId)
.setRange(null, null, false, 100) // Limit to 100 logins
.execute()
.get();
Collection sharedByLogins = new LinkedHashSet();
for (HColumn column : result.getColumns()) {
sharedByLogins.add(column.getValue());
}
return sharedByLogins;
}
@Override
public boolean hasBeenShared(String statusId) {
int zeroOrOne = HFactory.createCountQuery(keyspaceOperator, StringSerializer.get(), LongSerializer.get())
.setColumnFamily(SHARES_CF)
.setKey(statusId)
.setRange(null, null, 1)
.execute()
.get();
return zeroOrOne > 0;
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/cassandra/CassandraStatusAttachmentRepository.java
================================================
package fr.ippon.tatami.repository.cassandra;
import fr.ippon.tatami.config.Constants;
import fr.ippon.tatami.repository.StatusAttachmentRepository;
import me.prettyprint.cassandra.serializers.LongSerializer;
import me.prettyprint.cassandra.serializers.StringSerializer;
import me.prettyprint.cassandra.serializers.UUIDSerializer;
import me.prettyprint.cassandra.service.template.ColumnFamilyResult;
import me.prettyprint.cassandra.service.template.ColumnFamilyTemplate;
import me.prettyprint.cassandra.service.template.ThriftColumnFamilyTemplate;
import me.prettyprint.hector.api.Keyspace;
import me.prettyprint.hector.api.factory.HFactory;
import me.prettyprint.hector.api.mutation.Mutator;
import org.springframework.stereotype.Repository;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.UUID;
import static fr.ippon.tatami.config.ColumnFamilyKeys.STATUS_ATTACHMENT_CF;
/**
* Cassandra implementation of the StatusAttachmentRepository repository.
*
* Structure :
* - Key = statusId
* - Name = attachmentId
* - Value = time
*
* @author Julien Dubois
*/
@Repository
public class CassandraStatusAttachmentRepository
implements StatusAttachmentRepository {
private ColumnFamilyTemplate attachmentsTemplate;
@Inject
private Keyspace keyspaceOperator;
@PostConstruct
public void init() {
attachmentsTemplate = new ThriftColumnFamilyTemplate(keyspaceOperator,
STATUS_ATTACHMENT_CF,
StringSerializer.get(),
UUIDSerializer.get());
attachmentsTemplate.setCount(Constants.CASSANDRA_MAX_COLUMNS);
}
@Override
public void addAttachmentId(String statusId, String attachmentId) {
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.insert(statusId, STATUS_ATTACHMENT_CF, HFactory.createColumn(UUID.fromString(attachmentId),
Calendar.getInstance().getTimeInMillis(), UUIDSerializer.get(), LongSerializer.get()));
}
@Override
public void removeAttachmentId(String statusId, String attachmentId) {
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.delete(statusId, STATUS_ATTACHMENT_CF, UUID.fromString(attachmentId), UUIDSerializer.get());
}
@Override
public Collection findAttachmentIds(String statusId) {
ColumnFamilyResult result = attachmentsTemplate.queryColumns(statusId);
Collection attachmentIds = new ArrayList();
for (UUID columnName : result.getColumnNames()) {
attachmentIds.add(columnName.toString());
}
return attachmentIds;
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/cassandra/CassandraStatusReportRepository.java
================================================
package fr.ippon.tatami.repository.cassandra;
import fr.ippon.tatami.config.Constants;
import fr.ippon.tatami.config.GroupRoles;
import fr.ippon.tatami.repository.StatusReportRepository;
import me.prettyprint.cassandra.serializers.LongSerializer;
import me.prettyprint.cassandra.serializers.StringSerializer;
import me.prettyprint.cassandra.service.ColumnSliceIterator;
import me.prettyprint.cassandra.service.template.ColumnFamilyResult;
import me.prettyprint.cassandra.service.template.ColumnFamilyTemplate;
import me.prettyprint.cassandra.service.template.ColumnFamilyUpdater;
import me.prettyprint.cassandra.service.template.ThriftColumnFamilyTemplate;
import me.prettyprint.hector.api.Keyspace;
import me.prettyprint.hector.api.beans.ColumnSlice;
import me.prettyprint.hector.api.beans.HColumn;
import me.prettyprint.hector.api.exceptions.HectorException;
import me.prettyprint.hector.api.factory.HFactory;
import me.prettyprint.hector.api.mutation.Mutator;
import me.prettyprint.hector.api.query.SliceQuery;
import org.springframework.stereotype.Repository;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import java.util.*;
import static fr.ippon.tatami.config.ColumnFamilyKeys.GROUP_MEMBERS_CF;
import static fr.ippon.tatami.config.ColumnFamilyKeys.STATUS_REPORT_CF;
import static me.prettyprint.hector.api.factory.HFactory.createSliceQuery;
@Repository
public class CassandraStatusReportRepository implements StatusReportRepository {
private ColumnFamilyTemplate reportedStatusTemplate;
@Inject
private Keyspace keyspaceOperator;
@PostConstruct
public void init() {
reportedStatusTemplate = new ThriftColumnFamilyTemplate(keyspaceOperator,
STATUS_REPORT_CF,
StringSerializer.get(),
StringSerializer.get());
reportedStatusTemplate.setCount(Constants.CASSANDRA_MAX_COLUMNS);
}
@Override
public void reportStatus(String domain, String reportedStatusId, String reportingLogin) {
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.insert(domain, STATUS_REPORT_CF, HFactory.createStringColumn(reportedStatusId, reportingLogin));
}
@Override
public void unreportStatus(String domain, String reportedStatusId) {
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.delete(domain, STATUS_REPORT_CF, reportedStatusId, StringSerializer.get());
}
@Override
public List findReportedStatuses(String domain) {
SliceQuery query = HFactory.createSliceQuery(keyspaceOperator, StringSerializer.get(),
StringSerializer.get(), StringSerializer.get()).
setKey(domain).setColumnFamily(STATUS_REPORT_CF);
ColumnSliceIterator iterator =
new ColumnSliceIterator(query, null, "\uFFFF", false);
List reportedStatuses = new ArrayList();
while (iterator.hasNext()) {
reportedStatuses.add(iterator.next().getName());
}
return reportedStatuses;
}
public String findUserHavingReported(String domain, String statusId){
ColumnFamilyResult res = reportedStatusTemplate.queryColumns(domain);
return res.getString(statusId);
}
@Override
public boolean hasBeenReportedByUser(String domain, String reportedStatusId, String login) {
return login.equals(reportedStatusTemplate.queryColumns(domain).getString(reportedStatusId));
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/cassandra/CassandraStatusRepository.java
================================================
package fr.ippon.tatami.repository.cassandra;
import fr.ippon.tatami.domain.Attachment;
import fr.ippon.tatami.domain.Group;
import fr.ippon.tatami.repository.*;
import fr.ippon.tatami.service.util.DomainUtil;
import me.prettyprint.cassandra.serializers.StringSerializer;
import me.prettyprint.cassandra.service.template.ColumnFamilyResult;
import me.prettyprint.cassandra.service.template.ColumnFamilyTemplate;
import me.prettyprint.cassandra.service.template.ColumnFamilyUpdater;
import me.prettyprint.cassandra.service.template.ThriftColumnFamilyTemplate;
import me.prettyprint.cassandra.utils.TimeUUIDUtils;
import me.prettyprint.hector.api.Keyspace;
import me.prettyprint.hector.api.factory.HFactory;
import me.prettyprint.hector.api.mutation.Mutator;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Repository;
import fr.ippon.tatami.config.ColumnFamilyKeys;
import fr.ippon.tatami.domain.status.*;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.validation.*;
import java.util.*;
/**
* Cassandra implementation of the status repository.
*
* Timeline and Userline have the same structure :
* - Key : login
* - Name : status Id
* - Value : ""
*
* @author Julien Dubois
*/
@Repository
public class CassandraStatusRepository implements StatusRepository {
private final Logger log = LoggerFactory.getLogger(CassandraStatusRepository.class);
private static final String LOGIN = "login";
private static final String TYPE = "type";
private static final String USERNAME = "username";
private static final String DOMAIN = "domain";
private static final String STATUS_DATE = "statusDate";
//Normal status
private static final String STATUS_PRIVATE = "statusPrivate";
private static final String GROUP_ID = "groupId";
private static final String HAS_ATTACHMENTS = "hasAttachments";
private static final String CONTENT = "content";
private static final String DISCUSSION_ID = "discussionId";
private static final String REPLY_TO = "replyTo";
private static final String REPLY_TO_USERNAME = "replyToUsername";
private static final String REMOVED = "removed";
private static final String GEO_LOCALIZATION = "geoLocalization";
//Share, Mention Share & Announcement
private static final String ORIGINAL_STATUS_ID = "originalStatusId";
//Mention Friend
private static final String FOLLOWER_LOGIN = "followerLogin";
//Bean validation
private static final ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
private static final Validator validator = factory.getValidator();
//Cassandra Template
ColumnFamilyTemplate template;
@Inject
private Keyspace keyspaceOperator;
@Inject
private DiscussionRepository discussionRepository;
@Inject
private SharesRepository sharesRepository;
@Inject
private StatusAttachmentRepository statusAttachmentRepository;
@Inject
private AttachmentRepository attachmentRepository;
@PostConstruct
public void init() {
template =
new ThriftColumnFamilyTemplate(
keyspaceOperator,
ColumnFamilyKeys.STATUS_CF,
StringSerializer.get(),
StringSerializer.get());
}
@Override
public Status createStatus(String login,
boolean statusPrivate,
Group group,
Collection attachmentIds,
String content,
String discussionId,
String replyTo,
String replyToUsername,
String geoLocalization)
throws ConstraintViolationException {
Status status = new Status();
status.setLogin(login);
status.setType(StatusType.STATUS);
String username = DomainUtil.getUsernameFromLogin(login);
status.setUsername(username);
String domain = DomainUtil.getDomainFromLogin(login);
status.setDomain(domain);
status.setContent(content);
Set> constraintViolations = validator.validate(status);
if (!constraintViolations.isEmpty()) {
if (log.isDebugEnabled()) {
for (ConstraintViolation cv : constraintViolations) {
log.debug("Constraint violation: {}", cv.getMessage());
}
}
throw new ConstraintViolationException(new HashSet>(constraintViolations));
}
ColumnFamilyUpdater updater = this.createBaseStatus(status);
updater.setString(CONTENT, content);
status.setStatusPrivate(statusPrivate);
updater.setBoolean(STATUS_PRIVATE, statusPrivate);
if (group != null) {
String groupId = group.getGroupId();
status.setGroupId(groupId);
updater.setString(GROUP_ID, groupId);
}
if (attachmentIds != null && attachmentIds.size() > 0) {
status.setHasAttachments(true);
updater.setBoolean(HAS_ATTACHMENTS, true);
}
if (discussionId != null) {
status.setDiscussionId(discussionId);
updater.setString(DISCUSSION_ID, discussionId);
}
if (replyTo != null) {
status.setReplyTo(replyTo);
updater.setString(REPLY_TO, replyTo);
}
if (replyToUsername != null) {
status.setReplyToUsername(replyToUsername);
updater.setString(REPLY_TO_USERNAME, replyToUsername);
}
if(geoLocalization!=null) {
status.setGeoLocalization(geoLocalization);
updater.setString(GEO_LOCALIZATION, geoLocalization);
}
log.debug("Persisting Status : {}", status);
template.update(updater);
return status;
}
@Override
public Share createShare(String login, String originalStatusId) {
Share share = new Share();
share.setLogin(login);
share.setType(StatusType.SHARE);
String username = DomainUtil.getUsernameFromLogin(login);
share.setUsername(username);
String domain = DomainUtil.getDomainFromLogin(login);
share.setDomain(domain);
ColumnFamilyUpdater updater = this.createBaseStatus(share);
updater.setString(ORIGINAL_STATUS_ID, originalStatusId);
share.setOriginalStatusId(originalStatusId);
log.debug("Persisting Share : {}", share);
template.update(updater);
return share;
}
@Override
public Announcement createAnnouncement(String login, String originalStatusId) {
Announcement announcement = new Announcement();
announcement.setLogin(login);
announcement.setType(StatusType.ANNOUNCEMENT);
String username = DomainUtil.getUsernameFromLogin(login);
announcement.setUsername(username);
String domain = DomainUtil.getDomainFromLogin(login);
announcement.setDomain(domain);
ColumnFamilyUpdater updater = this.createBaseStatus(announcement);
updater.setString(ORIGINAL_STATUS_ID, originalStatusId);
announcement.setOriginalStatusId(originalStatusId);
log.debug("Persisting Announcement : {}", announcement);
template.update(updater);
return announcement;
}
@Override
public MentionFriend createMentionFriend(String login, String followerLogin) {
MentionFriend mentionFriend = new MentionFriend();
mentionFriend.setLogin(login);
mentionFriend.setType(StatusType.MENTION_FRIEND);
String username = DomainUtil.getUsernameFromLogin(login);
mentionFriend.setUsername(username);
String domain = DomainUtil.getDomainFromLogin(login);
mentionFriend.setDomain(domain);
ColumnFamilyUpdater updater = this.createBaseStatus(mentionFriend);
updater.setString(FOLLOWER_LOGIN, followerLogin);
log.debug("Persisting MentionFriend : {}", mentionFriend);
template.update(updater);
return mentionFriend;
}
@Override
public MentionShare createMentionShare(String login, String originalStatusId) {
MentionShare mentionShare = new MentionShare();
mentionShare.setLogin(login);
mentionShare.setType(StatusType.MENTION_SHARE);
String username = DomainUtil.getUsernameFromLogin(login);
mentionShare.setUsername(username);
String domain = DomainUtil.getDomainFromLogin(login);
mentionShare.setDomain(domain);
ColumnFamilyUpdater updater = this.createBaseStatus(mentionShare);
updater.setString(ORIGINAL_STATUS_ID, originalStatusId);
mentionShare.setOriginalStatusId(originalStatusId);
log.debug("Persisting MentionShare : {}", mentionShare);
template.update(updater);
return mentionShare;
}
private ColumnFamilyUpdater createBaseStatus(AbstractStatus abstractStatus) {
// Generate statusId and statusDate for all statuses
String statusId = TimeUUIDUtils.getUniqueTimeUUIDinMillis().toString();
abstractStatus.setStatusId(statusId);
ColumnFamilyUpdater updater = template.createUpdater(statusId);
Date statusDate = Calendar.getInstance().getTime();
updater.setDate(STATUS_DATE, statusDate);
abstractStatus.setStatusDate(statusDate);
// Persist common data : login, username, domain, type
String login = abstractStatus.getLogin();
if (login == null) {
throw new IllegalStateException("Login cannot be null for status: " + abstractStatus);
}
updater.setString(LOGIN, login);
String username = abstractStatus.getUsername();
if (username == null) {
throw new IllegalStateException("Username cannot be null for status: " + abstractStatus);
}
updater.setString(USERNAME, username);
String domain = abstractStatus.getDomain();
if (domain == null) {
throw new IllegalStateException("Domain cannot be null for status: " + abstractStatus);
}
updater.setString(DOMAIN, domain);
updater.setString(TYPE, abstractStatus.getType().name());
return updater;
}
@Override
@Cacheable("status-cache")
public AbstractStatus findStatusById(String statusId) {
if (statusId == null || statusId.equals("")) {
return null;
}
if (log.isTraceEnabled()) {
log.trace("Finding status : " + statusId);
}
ColumnFamilyResult result = template.queryColumns(statusId);
if (result.hasResults() == false) {
return null; // No status was found
}
AbstractStatus status = null;
String type = result.getString(TYPE);
if (type == null || type.equals(StatusType.STATUS.name())) {
status = findStatus(result, statusId);
} else if (type.equals(StatusType.SHARE.name())) {
status = findShare(result);
} else if (type.equals(StatusType.ANNOUNCEMENT.name())) {
status = findAnnouncement(result);
} else if (type.equals(StatusType.MENTION_FRIEND.name())) {
status = findMentionFriend(result);
} else if (type.equals(StatusType.MENTION_SHARE.name())) {
status = findMentionShare(result);
} else {
throw new IllegalStateException("Status has an unknown type: " + type);
}
if (status == null) { // Status was not found, or was removed
return null;
}
status.setStatusId(statusId);
status.setLogin(result.getString(LOGIN));
status.setUsername(result.getString(USERNAME));
String domain = result.getString(DOMAIN);
if (domain != null) {
status.setDomain(domain);
} else {
throw new IllegalStateException("Status cannot have a null domain: " + status);
}
status.setStatusDate(result.getDate(STATUS_DATE));
Boolean removed = result.getBoolean(REMOVED);
if (removed != null) {
status.setRemoved(removed);
}
return status;
}
private Status findStatus(ColumnFamilyResult result, String statusId) {
Status status = new Status();
status.setStatusId(statusId);
status.setType(StatusType.STATUS);
status.setContent(result.getString(CONTENT));
status.setStatusPrivate(result.getBoolean(STATUS_PRIVATE));
status.setGroupId(result.getString(GROUP_ID));
status.setHasAttachments(result.getBoolean(HAS_ATTACHMENTS));
status.setDiscussionId(result.getString(DISCUSSION_ID));
status.setReplyTo(result.getString(REPLY_TO));
status.setReplyToUsername(result.getString(REPLY_TO_USERNAME));
status.setGeoLocalization(result.getString(GEO_LOCALIZATION));
status.setRemoved(result.getBoolean(REMOVED));
if (status.getRemoved() == Boolean.TRUE) {
return null;
}
status.setDetailsAvailable(computeDetailsAvailable(status));
if (status.getHasAttachments() != null && status.getHasAttachments()) {
Collection attachmentIds = statusAttachmentRepository.findAttachmentIds(statusId);
Collection attachments = new ArrayList();
for (String attachmentId : attachmentIds) {
Attachment attachment = attachmentRepository.findAttachmentMetadataById(attachmentId);
if (attachment != null) {
// We copy everything excepted the attachment content, as we do not want it in the status cache
Attachment attachmentCopy = new Attachment();
attachmentCopy.setAttachmentId(attachmentId);
attachmentCopy.setSize(attachment.getSize());
attachmentCopy.setFilename(attachment.getFilename());
attachments.add(attachment);
}
}
status.setAttachments(attachments);
}
return status;
}
private Share findShare(ColumnFamilyResult result) {
Share share = new Share();
share.setType(StatusType.SHARE);
share.setOriginalStatusId(result.getString(ORIGINAL_STATUS_ID));
return share;
}
private Announcement findAnnouncement(ColumnFamilyResult result) {
Announcement announcement = new Announcement();
announcement.setType(StatusType.ANNOUNCEMENT);
announcement.setOriginalStatusId(result.getString(ORIGINAL_STATUS_ID));
return announcement;
}
private MentionFriend findMentionFriend(ColumnFamilyResult result) {
MentionFriend mentionFriend = new MentionFriend();
mentionFriend.setType(StatusType.MENTION_FRIEND);
mentionFriend.setFollowerLogin(result.getString(FOLLOWER_LOGIN));
return mentionFriend;
}
private MentionShare findMentionShare(ColumnFamilyResult result) {
MentionShare mentionShare = new MentionShare();
mentionShare.setType(StatusType.MENTION_SHARE);
mentionShare.setOriginalStatusId(result.getString(ORIGINAL_STATUS_ID));
return mentionShare;
}
@Override
@CacheEvict(value = "status-cache", key = "#status.statusId")
public void removeStatus(AbstractStatus status) {
log.debug("Removing Status : {}", status);
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.addDeletion(status.getStatusId(), ColumnFamilyKeys.STATUS_CF);
mutator.execute();
}
private boolean computeDetailsAvailable(Status status) {
boolean detailsAvailable = false;
if (status.getType().equals(StatusType.STATUS)) {
if (StringUtils.isNotBlank(status.getReplyTo())) {
detailsAvailable = true;
} else if (discussionRepository.hasReply(status.getStatusId())) {
detailsAvailable = true;
} else if (sharesRepository.hasBeenShared(status.getStatusId())) {
detailsAvailable = true;
}
}
return detailsAvailable;
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/cassandra/CassandraTagCounterRepository.java
================================================
package fr.ippon.tatami.repository.cassandra;
import fr.ippon.tatami.repository.TagCounterRepository;
import me.prettyprint.cassandra.model.thrift.ThriftCounterColumnQuery;
import me.prettyprint.cassandra.serializers.StringSerializer;
import me.prettyprint.hector.api.Keyspace;
import me.prettyprint.hector.api.factory.HFactory;
import me.prettyprint.hector.api.mutation.Mutator;
import me.prettyprint.hector.api.query.CounterQuery;
import org.springframework.stereotype.Repository;
import javax.inject.Inject;
import static fr.ippon.tatami.config.ColumnFamilyKeys.TAG_COUNTER_CF;
/**
* Cassandra implementation of the Tag Counter repository.
*
* Structure :
* - Key = tag + domain
* - Name = TAG_COUNTER
* - Value = count
*
* @author Julien Dubois
*/
@Repository
public class CassandraTagCounterRepository implements TagCounterRepository {
private static final String TAG_COUNTER = "TAG_COUNTER";
@Inject
private Keyspace keyspaceOperator;
@Override
public long getTagCounter(String domain, String tag) {
CounterQuery counter =
new ThriftCounterColumnQuery(keyspaceOperator,
StringSerializer.get(),
StringSerializer.get());
counter.setColumnFamily(TAG_COUNTER_CF).setKey(getKey(domain, tag)).setName(TAG_COUNTER);
return counter.execute().get().getValue();
}
@Override
public void incrementTagCounter(String domain, String tag) {
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.incrementCounter(getKey(domain, tag), TAG_COUNTER_CF, TAG_COUNTER, 1);
}
@Override
public void decrementTagCounter(String domain, String tag) {
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.decrementCounter(getKey(domain, tag), TAG_COUNTER_CF, TAG_COUNTER, 1);
}
@Override
public void deleteTagCounter(String domain, String tag) {
Mutator mutator = HFactory.createMutator(keyspaceOperator, StringSerializer.get());
mutator.addCounterDeletion(getKey(domain, tag), TAG_COUNTER_CF, TAG_COUNTER, StringSerializer.get());
mutator.execute();
}
/**
* Generates the key for this column family.
*/
private String getKey(String domain, String tag) {
return tag.toLowerCase() + "-" + domain;
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/cassandra/CassandraTagFollowerRepository.java
================================================
package fr.ippon.tatami.repository.cassandra;
import fr.ippon.tatami.repository.TagFollowerRepository;
import org.springframework.stereotype.Repository;
import fr.ippon.tatami.config.ColumnFamilyKeys;
import java.util.Collection;
/**
* Cassandra implementation of the Follower repository.
*
* Structure :
* - Key = tag + domain
* - Name = follower login
* - Value = time
*
* @author Julien Dubois
*/
@Repository
public class CassandraTagFollowerRepository
extends AbstractCassandraFollowerRepository
implements TagFollowerRepository {
@Override
public void addFollower(String domain, String tag, String login) {
super.addFollower(getKey(domain, tag), login);
}
@Override
public void removeFollower(String domain, String tag, String login) {
super.removeFollower(getKey(domain, tag), login);
}
@Override
public Collection findFollowers(String domain, String tag) {
return super.findFollowers(getKey(domain, tag));
}
@Override
public String getFollowersCF() {
return ColumnFamilyKeys.TAG_FOLLOWERS_CF;
}
/**
* Generates the key for this column family.
*/
private String getKey(String domain, String tag) {
return tag.toLowerCase() + "-" + domain;
}
}
================================================
FILE: services/src/main/java/fr/ippon/tatami/repository/cassandra/CassandraTaglineRepository.java
================================================
package fr.ippon.tatami.repository.cassandra;
import fr.ippon.tatami.domain.status.Status;
import fr.ippon.tatami.repository.TaglineRepository;
import me.prettyprint.cassandra.serializers.StringSerializer;
import me.prettyprint.cassandra.serializers.UUIDSerializer;
import me.prettyprint.hector.api.Keyspace;
import me.prettyprint.hector.api.factory.HFactory;
import me.prettyprint.hector.api.mutation.Mutator;
import org.springframework.stereotype.Repository;
import javax.inject.Inject;
import java.util.Collection;
import java.util.List;
import java.util.UUID;
import static fr.ippon.tatami.config.ColumnFamilyKeys.TAGLINE_CF;
/**
* Cassandra implementation of the Tag line repository.
*