diff --git a/package.json b/package.json
index 9ede333..046816d 100644
--- a/package.json
+++ b/package.json
@@ -9,8 +9,13 @@
"not op_mini all"
],
"dependencies": {
+ "@types/jest": "24.0.12",
+ "@types/node": "11.13.8",
+ "@types/react": "16.8.15",
+ "@types/react-dom": "16.8.4",
"react": "16.8.6",
- "react-dom": "16.8.6"
+ "react-dom": "16.8.6",
+ "typescript": "3.3.4000"
},
"devDependencies": {
"@hellroot/eslint-config": "1.8.0",
diff --git a/public/index.html b/public/index.html
index 9a8ef8f..5d57a79 100644
--- a/public/index.html
+++ b/public/index.html
@@ -8,7 +8,7 @@
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
-
React App
+ Яндекс.Почта
diff --git a/src/app/app.css b/src/app/app.css
deleted file mode 100644
index 1c4d511..0000000
--- a/src/app/app.css
+++ /dev/null
@@ -1,27 +0,0 @@
-.app {
- text-align: center;
-}
-
-.app-header {
- display: flex;
- min-height: 100vh;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- background-color: #282c34;
- color: #fff;
- font-size: calc(10px + 2vmin);
-}
-
-.app-link {
- color: #61dafb;
-}
-
-@keyframes app-logo-spin {
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
-}
diff --git a/src/app/app.jsx b/src/app/app.jsx
deleted file mode 100644
index f759eed..0000000
--- a/src/app/app.jsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import React, { Component } from 'react';
-
-import './app.css';
-
-export class App extends Component {
- render() {
- return (
-
- );
- }
-}
diff --git a/src/app/app.test.jsx b/src/app/app.test.jsx
deleted file mode 100644
index 81be6fa..0000000
--- a/src/app/app.test.jsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import React from 'react';
-import ReactDOM from 'react-dom';
-
-import { App } from './app';
-
-it('renders without crashing', () => {
- const div = document.createElement('div');
-
- ReactDOM.render(, div);
- ReactDOM.unmountComponentAtNode(div);
-});
diff --git a/src/app/app.tsx b/src/app/app.tsx
new file mode 100644
index 0000000..ea8a15d
--- /dev/null
+++ b/src/app/app.tsx
@@ -0,0 +1,9 @@
+import * as React from 'react';
+
+import { Page } from './page/page';
+
+export class App extends React.Component {
+ public render() {
+ return ;
+ }
+}
diff --git a/src/app/content/content.module.css b/src/app/content/content.module.css
new file mode 100644
index 0000000..4facc39
--- /dev/null
+++ b/src/app/content/content.module.css
@@ -0,0 +1,37 @@
+.content {
+ display: inline-block;
+ width: calc(100% - 226px);
+ min-width: 574px;
+ min-height: 492px;
+ margin-right: 20px;
+ background-color: #ffffff;
+ border-radius: 3px;
+ box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.34);
+ font-family: HelveticaNeue, serif;
+}
+
+.contentDark {
+ background-color: #555555;
+ border-radius: 3px;
+ box-shadow: 0 0 3px 3px #777777;
+ composes: content;
+}
+
+.myCheckbox {
+ width: 16px;
+ height: 16px;
+ margin-top: 11px;
+ margin-left: 20px;
+ border: solid 1px rgba(0, 0, 0, 0.15);
+ background-color: #ffffff;
+ border-radius: 3px;
+ composes: myInput from '../page/page.module.css';
+}
+
+.myCheckbox:checked {
+ background-clip: content-box;
+ background-color: white;
+ background-image: url('../../images/check.png');
+ background-size: 14px;
+ background-repeat: no-repeat;
+}
diff --git a/src/app/content/content.module.css.d.ts b/src/app/content/content.module.css.d.ts
new file mode 100644
index 0000000..6e2d3a5
--- /dev/null
+++ b/src/app/content/content.module.css.d.ts
@@ -0,0 +1,3 @@
+export const content: string;
+export const contentDark: string;
+export const myCheckbox: string;
diff --git a/src/app/content/content.tsx b/src/app/content/content.tsx
new file mode 100644
index 0000000..cad0df1
--- /dev/null
+++ b/src/app/content/content.tsx
@@ -0,0 +1,104 @@
+import React from 'react';
+
+import * as styles from './content.module.css';
+import * as pageStyles from '../page/page.module.css';
+
+import { MessageMenu } from '../messageMenu/messageMenu';
+import { Letters } from '../letters/letters';
+import { Letter } from '../letter/letter';
+import { Footer } from '../footer/footer';
+import { ILetterType } from '../types/types';
+
+interface IContentProps {
+ deleteMails: () => void;
+ letters: ILetterType[];
+ selectAll: () => void;
+ isSelectAll: boolean;
+ checkboxChange: (id: string) => void;
+ checked: { [id: string]: boolean };
+ text: string[];
+ setText: (text: string[]) => void;
+ setRead: (id: string) => void;
+ removeAddAnimation: (id: string) => void;
+ removeDeleteAnimation: (id: string) => void;
+ theme: boolean;
+ searchText: string;
+}
+
+interface IContentState {
+ letterIsVisible: boolean;
+}
+
+export class Content extends React.Component {
+ public constructor(props: IContentProps) {
+ super(props);
+
+ this.state = {
+ letterIsVisible: false,
+ };
+
+ this.showLetter = this.showLetter.bind(this);
+ this.closeLetter = this.closeLetter.bind(this);
+ this.getContentClass = this.getContentClass.bind(this);
+ this.getLineClass = this.getLineClass.bind(this);
+ }
+
+ private showLetter() {
+ this.setState({
+ letterIsVisible: true
+ });
+ }
+
+ private closeLetter() {
+ this.setState({
+ letterIsVisible: false
+ });
+ }
+
+ private getContentClass() {
+ return !this.props.theme ? styles.content : styles.contentDark;
+ }
+
+ private getLineClass() {
+ return !this.props.theme ? pageStyles.line : pageStyles.lineDark;
+ }
+
+ public render() {
+ return (
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
diff --git a/src/app/footer/footer.module.css b/src/app/footer/footer.module.css
new file mode 100644
index 0000000..8d80f9f
--- /dev/null
+++ b/src/app/footer/footer.module.css
@@ -0,0 +1,39 @@
+.footer {
+ width: 100%;
+ min-width: 574px;
+
+ border-top: solid 2px #e2e2e2;
+ font-family: HelveticaNeue, serif;
+}
+
+.footerDark {
+ border-top: solid 2px #222222;
+ composes: footer;
+}
+
+.menuText {
+ font-size: 11px;
+ font-weight: normal;
+}
+
+.menuLink {
+ composes: menuText;
+ display: inline-block;
+ margin-right: 20px;
+}
+
+.menu {
+ padding: 0;
+ margin: 0;
+ float: right;
+ list-style-type: none;
+}
+
+.link {
+ color: #bbbbbb;
+ composes: myLink from '../page/page.module.css';
+}
+
+.myCopy {
+ color: #bbbbbb;
+}
diff --git a/src/app/footer/footer.module.css.d.ts b/src/app/footer/footer.module.css.d.ts
new file mode 100644
index 0000000..5a3c232
--- /dev/null
+++ b/src/app/footer/footer.module.css.d.ts
@@ -0,0 +1,7 @@
+export const footer: string;
+export const menuLink: string;
+export const menuText: string;
+export const menu: string;
+export const link: string;
+export const myCopy: string;
+export const footerDark: string;
diff --git a/src/app/footer/footer.tsx b/src/app/footer/footer.tsx
new file mode 100644
index 0000000..263a360
--- /dev/null
+++ b/src/app/footer/footer.tsx
@@ -0,0 +1,43 @@
+import * as React from 'react';
+
+import * as styles from './footer.module.css';
+
+interface IFooterProps {
+ theme: boolean;
+}
+
+export class Footer extends React.Component {
+ public constructor(props: IFooterProps) {
+ super(props);
+
+ this.getFooterClass = this.getFooterClass.bind(this);
+ }
+
+ private getFooterClass() {
+ return !this.props.theme ? styles.footer : styles.footerDark;
+ }
+
+ public render() {
+ return (
+
+ );
+ }
+}
diff --git a/src/app/header/header.module.css b/src/app/header/header.module.css
new file mode 100644
index 0000000..21fc43c
--- /dev/null
+++ b/src/app/header/header.module.css
@@ -0,0 +1,181 @@
+.title {
+ display: inline-block;
+ width: 153px;
+ height: 31px;
+ margin-top: 12px;
+ margin-left: 11px;
+}
+
+.logo {
+ height: 31px;
+}
+
+.menu {
+ display: inline-block;
+ margin-top: 14px;
+ margin-left: 22px;
+ vertical-align: top;
+}
+
+.navigation {
+ display: inline-block;
+ width: 181px;
+ margin-left: 19px;
+ font-family: HelveticaNeue, serif;
+ vertical-align: top;
+}
+
+.header {
+ height: 56px;
+}
+
+.rectangle {
+ width: 20px;
+ height: 2px;
+ margin-top: 5px;
+ background-color: #000000;
+}
+
+.rectangleDark {
+ background-color: #ffffff;
+ composes: rectangle;
+}
+
+.search {
+ color: white;
+ display: inline-block;
+ width: calc(100% - 499px);
+ min-width: 301px;
+ height: 32px;
+ margin-top: 11px;
+ margin-left: 114px;
+ background-color: #ffffff;
+ box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.2);
+ opacity: 0.5;
+ vertical-align: top;
+}
+
+.searchDark {
+ background-color: #555555;
+ box-shadow: inset 0 0 0 1px #333333;
+ opacity: 1;
+ composes: search;
+}
+
+.searchInput {
+ display: inline-block;
+ width: calc(100% - 25px);
+ min-width: 276px;
+ height: 32px;
+ padding-left: 16px;
+
+ border-style: none;
+ background: none;
+ color: #000000;
+ font-family: Yandex Sans, serif;
+ font-size: 15px;
+ line-height: 2.13;
+ outline: none;
+ vertical-align: middle;
+}
+
+.searchInputDark {
+ color: #ffffff;
+ composes: searchInput;
+}
+
+.searchInput::-webkit-search-cancel-button {
+ -webkit-appearance: none;
+}
+
+.searchInput::-webkit-input-placeholder {
+ color: black;
+}
+
+.searchInputDark::-webkit-search-cancel-button {
+ -webkit-appearance: none;
+}
+
+.searchInputDark::-webkit-input-placeholder {
+ color: white;
+}
+
+.searchCancelButton {
+ display: inline-block;
+ width: 9px;
+ height: 9px;
+
+ border-style: none;
+ -webkit-appearance: none;
+ background: url(../../images/cross-symbol.png) no-repeat;
+ background-size: 9px;
+ cursor: pointer;
+ outline: none;
+ vertical-align: middle;
+}
+
+.searchCancelButtonDark {
+ composes: searchCancelButton;
+ background: url(../../images/cross-symbol-dark.png) no-repeat;
+ background-size: 9px;
+}
+
+.changeThemeButton {
+ width: 80px;
+ height: 32px;
+
+ border: none;
+ -webkit-appearance: none;
+ border-radius: 3px;
+ cursor: pointer;
+ background-color: #333333;
+ outline: none;
+ display: inline-block;
+ vertical-align: top;
+ margin-top: 11px;
+ float: right;
+ margin-right: 26px;
+}
+
+.changeThemeButtonText {
+ display: inline;
+ padding-top: 7px;
+ color: #ffffff;
+ font-size: 10px;
+ font-weight: 500;
+ text-align: center;
+}
+
+.changeThemeButtonDark {
+ background-color: #6287bd;
+ composes: changeThemeButton;
+}
+
+.spinner {
+ width: 100px;
+ height: 4px;
+ animation: move 2s linear infinite;
+ background-color: black;
+}
+
+.spinnerDark {
+ background-color: white;
+ composes: spinner;
+}
+
+.spinnerHidden {
+ display: none;
+}
+
+
+@keyframes move {
+ 0% {
+ margin-left: 0;
+ }
+ 50% {
+ margin-left: calc(100% - 100px);
+ }
+ 100% {
+ margin-left: 0;
+ }
+}
diff --git a/src/app/header/header.module.css.d.ts b/src/app/header/header.module.css.d.ts
new file mode 100644
index 0000000..5f02a71
--- /dev/null
+++ b/src/app/header/header.module.css.d.ts
@@ -0,0 +1,19 @@
+export const title: string;
+export const logo: string;
+export const menu: string;
+export const navigation: string;
+export const header: string;
+export const rectangle: string;
+export const search: string;
+export const searchInput: string;
+export const searchCancelButton: string;
+export const changeThemeButton: string;
+export const changeThemeButtonText: string;
+export const rectangleDark: string;
+export const searchDark: string;
+export const searchInputDark: string;
+export const changeThemeButtonDark: string;
+export const searchCancelButtonDark: string;
+export const spinner: string;
+export const spinnerDark: string;
+export const spinnerHidden: string;
diff --git a/src/app/header/header.tsx b/src/app/header/header.tsx
new file mode 100644
index 0000000..ade11d1
--- /dev/null
+++ b/src/app/header/header.tsx
@@ -0,0 +1,109 @@
+import React from 'react';
+
+import * as styles from './header.module.css';
+
+const headerMail = require('../../images/header-mail.svg');
+const headerYandex = require('../../images/header-yandex.svg');
+
+const headerMailDark = require('../../images/header-mail-dark.svg');
+const headerYandexDark = require('../../images/header-yandex-dark.svg');
+
+interface IHeaderProps {
+ setSearchText: (text: string) => void;
+ changeTheme: () => void;
+ theme: boolean;
+ isSearch: boolean;
+}
+
+export class Header extends React.Component {
+ public constructor(props: IHeaderProps) {
+ super(props);
+
+ this.search = this.search.bind(this);
+ this.getHeaderYandex = this.getHeaderYandex.bind(this);
+ this.getHeaderMail = this.getHeaderMail.bind(this);
+ this.getRectangleClass = this.getRectangleClass.bind(this);
+ this.getSearchClass = this.getSearchClass.bind(this);
+ this.getSearchInputClass = this.getSearchInputClass.bind(this);
+ this.getSearchCancelButtonClass = this.getSearchCancelButtonClass.bind(this);
+ this.getChangeThemeButtonClass = this.getChangeThemeButtonClass.bind(this);
+ this.getSpinnerClass = this.getSpinnerClass.bind(this);
+ }
+
+ private getHeaderYandex() {
+ return !this.props.theme ? headerYandex : headerYandexDark;
+ }
+
+ private getHeaderMail() {
+ return !this.props.theme ? headerMail : headerMailDark;
+ }
+
+ private getRectangleClass() {
+ return !this.props.theme ? styles.rectangle : styles.rectangleDark;
+ }
+
+ private getSearchClass() {
+ return !this.props.theme ? styles.search : styles.searchDark;
+ }
+
+ private getSearchInputClass() {
+ return !this.props.theme ? styles.searchInput : styles.searchInputDark;
+ }
+
+ private getSearchCancelButtonClass() {
+ return !this.props.theme ? styles.searchCancelButton : styles.searchCancelButtonDark;
+ }
+
+ private getChangeThemeButtonClass() {
+ return !this.props.theme ? styles.changeThemeButton : styles.changeThemeButtonDark;
+ }
+
+ private search(event: React.ChangeEvent) {
+ this.props.setSearchText(event.target.value);
+ }
+
+ private getSpinnerClass() {
+ if (this.props.isSearch) {
+ if (!this.props.theme) {
+ return styles.spinner;
+ } else {
+ return styles.spinnerDark;
+ }
+ } else {
+ return styles.spinnerHidden;
+ }
+ }
+
+ public render() {
+ return (
+
+
+
+
})
+
})
+
+
+
+
+ );
+ }
+}
diff --git a/src/app/letter/letter.module.css b/src/app/letter/letter.module.css
new file mode 100644
index 0000000..f298aa1
--- /dev/null
+++ b/src/app/letter/letter.module.css
@@ -0,0 +1,44 @@
+.letter {
+ width: 100%;
+ min-width: 574px;
+ min-height: 415px;
+}
+
+.hidden {
+ display: none;
+}
+
+.closeImg {
+ display: inline-block;
+ width: 9px;
+ height: 9px;
+ margin-top: 15px;
+ margin-left: 10px;
+ opacity: 0.15;
+ vertical-align: top;
+}
+
+.myArticle {
+ display: inline-block;
+ width: calc(100% - 50px);
+ min-width: 520px;
+ margin-left: 20px;
+ font-size: 13px;
+}
+
+.text {
+ font-family: Arial, serif;
+ color: #000000;
+}
+
+.textDark {
+ color: #ffffff;
+ composes: text;
+}
+
+.image {
+ width: 140px;
+ margin-right: 1px;
+ float: left;
+ shape-outside: circle(50%);
+}
diff --git a/src/app/letter/letter.module.css.d.ts b/src/app/letter/letter.module.css.d.ts
new file mode 100644
index 0000000..298dfae
--- /dev/null
+++ b/src/app/letter/letter.module.css.d.ts
@@ -0,0 +1,7 @@
+export const letter: string;
+export const closeImg: string;
+export const myArticle: string;
+export const image: string;
+export const text: string;
+export const hidden: string;
+export const textDark: string;
diff --git a/src/app/letter/letter.tsx b/src/app/letter/letter.tsx
new file mode 100644
index 0000000..191f048
--- /dev/null
+++ b/src/app/letter/letter.tsx
@@ -0,0 +1,55 @@
+import * as React from 'react';
+
+import * as styles from './letter.module.css';
+
+interface ILetterProps {
+ text: string[];
+ display: boolean;
+ closeLetter: () => void;
+ theme: boolean;
+}
+
+const close: string = require('../../images/cross-symbol.png');
+const closeDark: string = require('../../images/cross-symbol-dark.png');
+
+export class Letter extends React.Component {
+ public constructor(props: ILetterProps) {
+ super(props);
+
+ this.makeClassName = this.makeClassName.bind(this);
+ this.getTextClass = this.getTextClass.bind(this);
+ this.getCloseImg = this.getCloseImg.bind(this);
+ }
+
+ private getTextClass() {
+ return !this.props.theme ? styles.text : styles.textDark;
+ }
+
+ private getCloseImg() {
+ return !this.props.theme ? close : closeDark;
+ }
+
+ private readonly makeClassName = () => {
+ return this.props.display ? styles.letter : styles.hidden;
+ };
+
+ public render(): React.ReactNode {
+ const letter = [];
+ for (let i = 0; i < this.props.text.length; i++) {
+ letter.push({this.props.text[i]}
);
+ }
+ return (
+
+ );
+ }
+}
diff --git a/src/app/letterHead/letterHead.module.css b/src/app/letterHead/letterHead.module.css
new file mode 100644
index 0000000..43b15b6
--- /dev/null
+++ b/src/app/letterHead/letterHead.module.css
@@ -0,0 +1,145 @@
+.myCheckbox {
+ width: 16px;
+ height: 16px;
+ margin-top: 11px;
+ margin-left: 20px;
+ border: solid 1px rgba(0, 0, 0, 0.15);
+ background-color: #ffffff;
+ border-radius: 3px;
+ composes: myInput from '../page/page.module.css';
+}
+
+.myCheckbox:checked {
+ background-clip: content-box;
+ background-color: white;
+ background-image: url('../../images/check.png');
+ background-size: 14px;
+ background-repeat: no-repeat;
+}
+
+.letterHead {
+ min-width: 574px;
+ height: 40px;
+}
+
+.hidden {
+ display: none;
+}
+
+.link {
+ outline: none;
+ text-decoration: none;
+}
+.unread {
+ font-weight: bold;
+ composes: link;
+}
+
+.authorImage {
+ display: inline-block;
+ width: 30px;
+ height: 30px;
+ margin-top: 5px;
+ margin-left: 10px;
+ border-radius: 50%;
+ vertical-align: top;
+}
+
+.authorName {
+ display: inline-block;
+ width: 160px;
+ height: 40px;
+ margin-left: 10px;
+ color: #000000;
+ font-size: 13px;
+ vertical-align: top;
+}
+
+.authorNameDark {
+ composes: authorName;
+ color: #ffffff;
+}
+
+.read {
+ display: inline-block;
+ width: 10px;
+ height: 10px;
+ border-radius: 50%;
+ vertical-align: super;
+}
+
+.unread > .read {
+ background-color: #6287bd;
+}
+
+.unreadDark {
+ composes: unread;
+}
+
+.unreadDark > .read {
+ background-color: #cccccc;
+}
+
+.text {
+ display: inline-block;
+ overflow: hidden;
+ width: calc(100% - 322px);
+ min-width: 240px;
+ height: 40px;
+ margin-left: 10px;
+ color: #000000;
+ font-size: 13px;
+ text-overflow: ellipsis;
+ vertical-align: top;
+ composes: myText from '../page/page.module.css';
+}
+
+.textDark {
+ color: #ffffff;
+ composes: text;
+}
+
+.date {
+ display: inline-block;
+ height: 40px;
+ margin-right: 10px;
+ color: #9b9b9b;
+ font-size: 13px;
+ font-weight: normal;
+ text-align: right;
+ vertical-align: bottom;
+}
+
+
+.dateDark {
+ color: #bbbbbb;
+ composes: date;
+}
+
+.animatedAddLine {
+ animation: add 1.5s linear;
+ composes: letterHead;
+}
+
+.animatedDeleteLine {
+ animation: delete 1.5s linear;
+ composes: letterHead;
+}
+
+@keyframes add {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+}
+
+@keyframes delete {
+ from {
+ opacity: 1;
+ }
+ to {
+ opacity: 0;
+ }
+}
diff --git a/src/app/letterHead/letterHead.module.css.d.ts b/src/app/letterHead/letterHead.module.css.d.ts
new file mode 100644
index 0000000..dd53404
--- /dev/null
+++ b/src/app/letterHead/letterHead.module.css.d.ts
@@ -0,0 +1,16 @@
+export const unread: string;
+export const myCheckbox: string;
+export const letterHead: string;
+export const link: string;
+export const authorImage: string;
+export const authorName: string;
+export const read: string;
+export const date: string;
+export const animatedAddLine: string;
+export const animatedDeleteLine: string;
+export const text: string;
+export const hidden: string;
+export const authorNameDark: string;
+export const unreadDark: string;
+export const textDark: string;
+export const dateDark: string;
diff --git a/src/app/letterHead/letterHead.tsx b/src/app/letterHead/letterHead.tsx
new file mode 100644
index 0000000..eb71811
--- /dev/null
+++ b/src/app/letterHead/letterHead.tsx
@@ -0,0 +1,131 @@
+import * as React from 'react';
+
+import * as styles from './letterHead.module.css';
+import * as pageStyles from '../page/page.module.css';
+
+interface ILetterHeadProps {
+ id: string;
+ authorName: string;
+ authorImage: string;
+ letterText: string[];
+ headText: string;
+ isVisible: boolean;
+ isChecked: boolean;
+ checkboxChange: (id: string) => void;
+ setText: (text: string[]) => void;
+ addAnimation: boolean;
+ removeAddAnimation: (id: string) => void;
+ removeDeleteAnimation: (id: string) => void;
+ deleteAnimation: boolean;
+ isRead: boolean;
+ setRead: (id: string) => void;
+ showLetter: () => void;
+ headTagDate: string;
+ headDate: string;
+ theme: boolean;
+}
+
+export class LetterHead extends React.Component {
+ public constructor(props: ILetterHeadProps) {
+ super(props);
+
+ this.makeClassName = this.makeClassName.bind(this);
+ this.makeLinkClassName = this.makeLinkClassName.bind(this);
+ this.deleteAnimation = this.deleteAnimation.bind(this);
+ this.getAuthorNameClass = this.getAuthorNameClass.bind(this);
+ this.getTextClass = this.getTextClass.bind(this);
+ this.getDateClass = this.getDateClass.bind(this);
+ this.getLineClass = this.getLineClass.bind(this);
+ }
+
+ private makeClassName() {
+ if (this.props.addAnimation) {
+ return styles.animatedAddLine;
+ }
+
+ if (this.props.deleteAnimation) {
+ return styles.animatedDeleteLine;
+ }
+
+ if (!this.props.isVisible) {
+ return styles.hidden;
+ }
+
+ return styles.letterHead;
+ }
+
+ private makeLinkClassName() {
+ if (this.props.isRead) {
+ if (!this.props.theme) {
+ return styles.unread;
+ }
+ return styles.unreadDark;
+ }
+ return styles.link;
+ }
+
+ private deleteAnimation() {
+ if (this.props.addAnimation) {
+ this.props.removeAddAnimation(this.props.id);
+ }
+ if (this.props.deleteAnimation) {
+ this.props.removeDeleteAnimation(this.props.id);
+ }
+ }
+
+ private getAuthorNameClass() {
+ return !this.props.theme ? styles.authorName : styles.authorNameDark;
+ }
+
+ private getTextClass() {
+ return !this.props.theme ? styles.text : styles.textDark;
+ }
+
+ private getDateClass() {
+ return !this.props.theme ? styles.date : styles.dateDark;
+ }
+
+ private getLineClass() {
+ return !this.props.theme ? pageStyles.line : pageStyles.lineDark;
+ }
+
+ public render() {
+ return (
+
+
+ {
+ this.props.setText(this.props.letterText);
+ this.props.showLetter();
+ this.props.setRead(this.props.id);
+ }}
+ >
+
+
+
{this.props.authorName}
+
+
+
+
{this.props.headText}
+
+
+
+
+
+
+
+ );
+ }
+}
diff --git a/src/app/letters/letters.module.css b/src/app/letters/letters.module.css
new file mode 100644
index 0000000..a953e60
--- /dev/null
+++ b/src/app/letters/letters.module.css
@@ -0,0 +1,12 @@
+.letters {
+ width: 100%;
+ min-height: 415px;
+ padding: 0;
+ margin: 0;
+ list-style-type: none;
+ vertical-align: top;
+}
+
+.hidden {
+ display: none;
+}
diff --git a/src/app/letters/letters.module.css.d.ts b/src/app/letters/letters.module.css.d.ts
new file mode 100644
index 0000000..87a75ba
--- /dev/null
+++ b/src/app/letters/letters.module.css.d.ts
@@ -0,0 +1,2 @@
+export const letters: string;
+export const hidden: string;
diff --git a/src/app/letters/letters.tsx b/src/app/letters/letters.tsx
new file mode 100644
index 0000000..1f79759
--- /dev/null
+++ b/src/app/letters/letters.tsx
@@ -0,0 +1,99 @@
+import * as React from 'react';
+
+import * as styles from './letters.module.css';
+
+import { LetterHead } from '../letterHead/letterHead';
+import { ILetterType } from '../types/types';
+
+interface ILettersProps {
+ letters: ILetterType[];
+ checkboxChange: (id: string) => void;
+ checked: { [id: string]: boolean };
+ setText: (text: string[]) => void;
+ setRead: (id: string) => void;
+ removeAddAnimation: (id: string) => void;
+ removeDeleteAnimation: (id: string) => void;
+ display: boolean;
+ showLetter: () => void;
+ theme: boolean;
+ searchText: string;
+}
+
+interface ILettersState {
+ worker: any;
+ searchFor: string;
+ filteredLetters: ILetterType[] | null;
+}
+
+export class Letters extends React.Component {
+ public constructor(props: ILettersProps) {
+ super(props);
+
+ this.state = {
+ worker: null,
+ searchFor: '',
+ filteredLetters: null
+ };
+
+ this.makeClassName = this.makeClassName.bind(this);
+ this.isLetterHasText = this.isLetterHasText.bind(this);
+ }
+
+ private makeClassName() {
+ return this.props.display ? styles.letters : styles.hidden;
+ }
+
+ private isLetterHasText = (text: string, letter: ILetterType) => {
+ if (text.length === 0) {
+ return true;
+ }
+ if (
+ letter.headText.toLocaleUpperCase().indexOf(text.toLocaleUpperCase()) !== -1 ||
+ letter.authorName.toLocaleUpperCase().indexOf(text.toLocaleUpperCase()) !== -1
+ ) {
+ return true;
+ }
+ let f = false;
+ for (let i = 0; i < letter.letterText.length && !f; i++) {
+ if (letter.letterText[i].toLocaleUpperCase().indexOf(text.toLocaleUpperCase()) !== -1) {
+ f = true;
+ }
+ }
+ return f;
+ };
+
+ // eslint-disable-next-line react/sort-comp
+ private static filterLetters(letters: ILetterType[]) {
+ let newLetters: ILetterType[] = letters;
+ newLetters = newLetters.map((letter: ILetterType, index: number) => {
+ letter.isVisible = index < 30;
+ return letter;
+ });
+ return newLetters;
+ }
+
+ public render() {
+ return (
+
+ {this.props.letters.map((letter: ILetterType, index: number) => {
+ if (index < 30) {
+ return (
+
+ );
+ }
+ })}
+
+ );
+ }
+}
diff --git a/src/app/messageMenu/messageMenu.module.css b/src/app/messageMenu/messageMenu.module.css
new file mode 100644
index 0000000..cffc772
--- /dev/null
+++ b/src/app/messageMenu/messageMenu.module.css
@@ -0,0 +1,21 @@
+.link {
+ color: #cccccc;
+ composes: myLink from '../page/page.module.css';
+}
+
+.text {
+ display: inline-block;
+ height: 40px;
+ margin-left: 20px;
+ font-size: 13px;
+ font-weight: 500;
+}
+
+.messageMenu {
+ display: inline-block;
+ height: 40px;
+ padding: 0;
+ margin: 0;
+ list-style-type: none;
+ vertical-align: top;
+}
diff --git a/src/app/messageMenu/messageMenu.module.css.d.ts b/src/app/messageMenu/messageMenu.module.css.d.ts
new file mode 100644
index 0000000..7592251
--- /dev/null
+++ b/src/app/messageMenu/messageMenu.module.css.d.ts
@@ -0,0 +1,3 @@
+export const text: string;
+export const link: string;
+export const messageMenu: string;
diff --git a/src/app/messageMenu/messageMenu.tsx b/src/app/messageMenu/messageMenu.tsx
new file mode 100644
index 0000000..1fcda89
--- /dev/null
+++ b/src/app/messageMenu/messageMenu.tsx
@@ -0,0 +1,37 @@
+import * as React from 'react';
+
+import * as styles from './messageMenu.module.css';
+import * as pageStyles from '../page/page.module.css';
+
+interface IMessageMenuProps {
+ deleteMessages: () => void;
+}
+
+export class MessageMenu extends React.Component {
+ public render() {
+ return (
+
+ );
+ }
+}
diff --git a/src/app/page/nav.tsx b/src/app/page/nav.tsx
new file mode 100644
index 0000000..cd07dfe
--- /dev/null
+++ b/src/app/page/nav.tsx
@@ -0,0 +1,100 @@
+import * as React from 'react';
+
+import * as pageStyles from './page.module.css';
+
+interface INavProps {
+ newLetter: () => void;
+ theme: boolean;
+}
+
+export class Nav extends React.Component {
+ public constructor(props: INavProps) {
+ super(props);
+
+ this.getWriteButtonClass = this.getWriteButtonClass.bind(this);
+ this.getNavigationContentCurrentClass = this.getNavigationContentCurrentClass.bind(this);
+ this.getNavigationTextClass = this.getNavigationTextClass.bind(this);
+ this.getNavigationTextCurrentClass = this.getNavigationTextCurrentClass.bind(this);
+ this.getNavigationContentClass = this.getNavigationContentClass.bind(this);
+ }
+
+ private getWriteButtonClass() {
+ return !this.props.theme
+ ? pageStyles.navigationWriteButton
+ : pageStyles.navigationWriteButtonDark;
+ }
+
+ private getNavigationContentCurrentClass() {
+ return !this.props.theme
+ ? pageStyles.navigationContentCurrent
+ : pageStyles.navigationContentCurrentDark;
+ }
+
+ private getNavigationTextClass() {
+ return !this.props.theme ? pageStyles.navigationText : pageStyles.navigationTextDark;
+ }
+
+ private getNavigationTextCurrentClass() {
+ return !this.props.theme
+ ? pageStyles.navigationTextCurrent
+ : pageStyles.navigationTextCurrentDark;
+ }
+
+ private getNavigationContentClass() {
+ return !this.props.theme ? pageStyles.navigationContent : pageStyles.navigationContentDark;
+ }
+
+ public render() {
+ return (
+
+ );
+ }
+}
diff --git a/src/app/page/page.module.css b/src/app/page/page.module.css
new file mode 100644
index 0000000..fb1dfaa
--- /dev/null
+++ b/src/app/page/page.module.css
@@ -0,0 +1,147 @@
+.page {
+ min-width: 800px;
+ min-height: 600px;
+}
+
+.myText {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.navigation {
+ display: inline-block;
+ width: 181px;
+
+ margin-left: 19px;
+ font-family: HelveticaNeue, serif;
+ vertical-align: top;
+}
+
+.navigationWriteButton {
+ width: 147px;
+ height: 32px;
+
+ border: none;
+ -webkit-appearance: none;
+ background-color: #6287bd;
+ border-radius: 3px;
+ cursor: pointer;
+ outline: none;
+}
+
+.navigationWriteButtonDark {
+ background-color: #333333;
+ composes: navigationWriteButton;
+}
+
+.navigationWriteText {
+ display: inline;
+ padding-top: 7px;
+ color: #ffffff;
+ font-size: 12px;
+ font-weight: 500;
+ text-align: center;
+}
+
+.navigationContent {
+ width: 147px;
+ height: 22px;
+ margin-top: 0;
+}
+
+.navigationContentDark {
+ composes: navigationContent;
+}
+
+.navigationContentCurrent {
+ background-color: #cdd6e4;
+ border-radius: 3px;
+ composes: navigationContent;
+}
+
+.navigationContentCurrentDark {
+ background-color: #666666;
+ composes: navigationContentCurrent;
+}
+
+.navigationContent:hover {
+ width: 147px;
+ height: 22px;
+ margin-top: 0;
+ background-color: #cdd6e4;
+ border-radius: 3px;
+}
+
+.navigationContentDark:hover {
+ width: 147px;
+ height: 22px;
+ margin-top: 0;
+ background-color: #666666;
+ border-radius: 3px;
+}
+
+.navigationText {
+ composes: myText;
+ display: block;
+ width: 130px;
+ padding-top: 6px;
+ padding-left: 10px;
+ color: #707070;
+ font-size: 11px;
+ font-weight: 500;
+}
+
+.navigationTextDark {
+ composes: navigationText;
+ color: #cccccc;
+}
+
+.navigationTextCurrent {
+ color: black;
+ composes: navigationText;
+ font-weight: bold;
+}
+
+.navigationTextCurrentDark {
+ color: #ffffff;
+ composes: navigationTextCurrent;
+}
+
+.myInput {
+ display: inline-block;
+ -webkit-appearance: none;
+}
+
+.line {
+ width: calc(100% - 2px);
+ min-width: 572px;
+ height: 0;
+
+ border: solid 1px #e2e2e2;
+ background-color: #e5eaf0;
+}
+
+.lineDark {
+ composes: line;
+ border: solid 1px #222222;
+ background-color: #222222;
+}
+
+.navigationList {
+ padding: 0;
+ margin: 8px 0 0;
+ list-style-type: none;
+ vertical-align: top;
+}
+
+.myLink {
+ outline: none;
+ text-decoration: none;
+}
+
+.navigationLink {
+ outline: none;
+ text-decoration: none;
+ text-overflow: ellipsis;
+}
diff --git a/src/app/page/page.module.css.d.ts b/src/app/page/page.module.css.d.ts
new file mode 100644
index 0000000..b6f84f8
--- /dev/null
+++ b/src/app/page/page.module.css.d.ts
@@ -0,0 +1,20 @@
+export const page: string;
+export const navigation: string;
+export const navigationWriteButton: string;
+export const navigationWriteText: string;
+export const navigationContent: string;
+export const navigationContentCurrent: string;
+export const navigationText: string;
+export const navigationTextCurrent: string;
+export const navigationLink: string;
+export const navigationList: string;
+export const myInput: string;
+export const myText: string;
+export const myLink: string;
+export const line: string;
+export const navigationWriteButtonDark: string;
+export const navigationContentCurrentDark: string;
+export const navigationContentDark: string;
+export const navigationTextDark: string;
+export const navigationTextCurrentDark: string;
+export const lineDark: string;
diff --git a/src/app/page/page.tsx b/src/app/page/page.tsx
new file mode 100644
index 0000000..ee62099
--- /dev/null
+++ b/src/app/page/page.tsx
@@ -0,0 +1,345 @@
+import * as React from 'react';
+
+import { Header } from '../header/header';
+import { Nav } from './nav';
+import { Content } from '../content/content';
+import {
+ genAuthorImage,
+ genAuthorName,
+ genHeadText,
+ genText,
+ getDate,
+ getHeadDate,
+ generateLetters,
+ MAX_LETTERS
+} from '../scripts/scripts';
+import { ILetterType } from '../types/types';
+import styles from './page.module.css';
+
+interface IMyState {
+ checked: { [id: string]: boolean };
+ letters: ILetterType[];
+ count: number;
+ isSelectAll: boolean;
+ text: string[];
+ searchText: string;
+ theme: boolean;
+ searchFor: string;
+ worker: any;
+ filteredLetters: ILetterType[] | null;
+ isSearch: boolean;
+}
+
+export class Page extends React.Component<{}, IMyState> {
+ public constructor(props: {}) {
+ super(props);
+ const { letters, checked } = generateLetters();
+ this.state = {
+ checked,
+ letters,
+ count: 0,
+ isSelectAll: false,
+ text: [],
+ searchText: '',
+ theme: false,
+ searchFor: '',
+ worker: null,
+ filteredLetters: null,
+ isSearch: false
+ };
+
+ this.newLetter = this.newLetter.bind(this);
+ this.deleteMails = this.deleteMails.bind(this);
+ this.selectAll = this.selectAll.bind(this);
+ this.checkboxChange = this.checkboxChange.bind(this);
+ this.setText = this.setText.bind(this);
+ this.markRead = this.markRead.bind(this);
+ this.setRead = this.setRead.bind(this);
+ this.removeAddAnimation = this.removeAddAnimation.bind(this);
+ this.setSearchText = this.setSearchText.bind(this);
+ this.deleteLetter = this.deleteLetter.bind(this);
+ this.removeLetterById = this.removeLetterById.bind(this);
+
+ this.last = 0;
+
+ this.newMail = this.newMail.bind(this);
+ this.changeTheme = this.changeTheme.bind(this);
+ document.body.style.background = this.state.theme ? 'black' : '#e5eaf0';
+ this.newMail();
+ }
+
+ public newLetter = () => {
+ console.log('1');
+ const id: string = `letter-id-${this.state.count}`;
+
+ this.setState((state: Readonly) => {
+ return { count: state.count + 1 };
+ });
+
+ const authorName: string = genAuthorName();
+ const authorImage: string = genAuthorImage();
+ const headText: string = genHeadText();
+ const letterText: string[] = genText();
+
+ const date: Date = new Date();
+ const headTagDate: string = getDate(date);
+ const headDate: string = getHeadDate(date);
+
+ const newChecked: { [id: string]: boolean } = this.state.checked;
+ newChecked[id] = false;
+
+ const newLetter: ILetterType = {
+ id,
+ letterText,
+ authorName,
+ authorImage,
+ headText,
+ isChecked: false,
+ isVisible: true,
+ isRead: true,
+ addAnimation: true,
+ deleteAnimation: false,
+ headTagDate,
+ headDate
+ };
+
+ this.setState((state: IMyState) => {
+ return {
+ letters: [newLetter].concat(state.letters),
+ checked: newChecked
+ };
+ });
+ };
+
+ private readonly selectAll = () => {
+ const newChecked: { [id: string]: boolean } = this.state.checked;
+
+ if (this.state.filteredLetters !== null) {
+ for (let i = 0; i < Math.min(this.state.filteredLetters.length, MAX_LETTERS); i++) {
+ newChecked[this.state.filteredLetters[i].id] = !this.state.isSelectAll;
+ }
+ } else {
+ for (let i = 0; i < Math.min(this.state.letters.length, MAX_LETTERS); i++) {
+ newChecked[this.state.letters[i].id] = !this.state.isSelectAll;
+ }
+ }
+ this.setState((state: IMyState) => {
+ return {
+ isSelectAll: !state.isSelectAll,
+ checked: newChecked
+ };
+ });
+ };
+
+ private readonly checkboxChange = (id: string) => {
+ const newChecked: { [id: string]: boolean } = this.state.checked;
+ newChecked[id] = !newChecked[id];
+ this.setState({
+ checked: newChecked
+ });
+ };
+
+ private readonly deleteMails = () => {
+ const newLetters: ILetterType[] = this.state.letters.map(letter => {
+ const newLetter: ILetterType = letter;
+ if (this.state.checked[newLetter.id]) {
+ newLetter.deleteAnimation = true;
+ }
+ return newLetter;
+ });
+
+ this.setState({
+ letters: newLetters,
+ isSelectAll: false
+ });
+
+ // setTimeout(this.makeDelete, 1500);
+ };
+
+ private readonly setText = (text: string[]) => {
+ this.setState({
+ text
+ });
+ };
+
+ private readonly markRead = (id: string, val: ILetterType) => {
+ const val1: ILetterType = val;
+ if (val1.id === id) {
+ val1.isRead = false;
+ }
+ return val1;
+ };
+
+ private readonly setRead = (id: string) => {
+ const newLetters: ILetterType[] = this.state.letters;
+ const letters1: ILetterType[] = newLetters.map(value => this.markRead(id, value));
+ this.setState({
+ letters: letters1
+ });
+ };
+
+ private readonly removeAddAnimation = (id: string) => {
+ const letters1: ILetterType[] = this.state.letters;
+ const newLetters: ILetterType[] = letters1.map(value => {
+ const tmp: ILetterType = value;
+ if (tmp.id === id) {
+ tmp.addAnimation = false;
+ }
+ return tmp;
+ });
+ this.setState({
+ letters: newLetters
+ });
+ };
+
+ private isLetterHasText = (text: string, letter: ILetterType) => {
+ if (text.length === 0) {
+ return true;
+ }
+ if (
+ letter.headText.toLocaleUpperCase().indexOf(text.toLocaleUpperCase()) !== -1 ||
+ letter.authorName.toLocaleUpperCase().indexOf(text.toLocaleUpperCase()) !== -1
+ ) {
+ return true;
+ }
+ let f = false;
+ for (let i = 0; i < letter.letterText.length && !f; i++) {
+ if (letter.letterText[i].toLocaleUpperCase().indexOf(text.toLocaleUpperCase()) !== -1) {
+ f = true;
+ }
+ }
+ return f;
+ };
+
+ private readonly setSearchText = (text: string) => {
+ this.setState({
+ searchText: text
+ });
+ };
+
+ private newMail() {
+ this.newLetter();
+ const fiveMinute = 300000;
+ const maxTime = 600000;
+ const minTime = 10;
+ const time: number = Math.max(
+ fiveMinute - this.last,
+ Math.floor(Math.random() * (maxTime - minTime) + minTime)
+ );
+ this.last = time;
+ setTimeout(this.newMail, time);
+ }
+
+ private removeLetterById(letters: ILetterType[], id: string) {
+ let newLetters: ILetterType[] = letters;
+ newLetters = newLetters.filter((letter: ILetterType) => letter.id !== id);
+ return newLetters;
+ }
+
+ private deleteLetter(id: string) {
+ this.setState((state: IMyState) => {
+ const newLetters: ILetterType[] = this.removeLetterById(state.letters, id);
+ let newFilteredLetters: ILetterType[] | null = null;
+ if (state.filteredLetters !== null) {
+ newFilteredLetters = this.removeLetterById(state.filteredLetters, id);
+ }
+ return {
+ letters: newLetters,
+ filteredLetters: newFilteredLetters
+ };
+ });
+ }
+
+ private changeTheme() {
+ this.setState((state: IMyState) => {
+ document.body.style.background = state.theme ? '#e5eaf0' : 'black';
+ return { theme: !state.theme };
+ });
+ }
+
+ private last: number;
+
+ public render() {
+ const searchText: string = this.state.searchText.toLocaleUpperCase();
+
+ if (searchText !== this.state.searchFor) {
+ if (this.state.searchText !== '') {
+ this.setState((state: IMyState) => {
+ if (state.worker) {
+ clearTimeout(state.worker);
+ }
+
+ return {
+ searchFor: searchText,
+ worker: setTimeout(() => {
+ const lambdaWorker = (filtered: ILetterType[], from: number, size: number) => {
+ const to = Math.min(from + 30, size);
+ const newLetters = filtered.concat(
+ state.letters
+ .slice(from, to)
+ .filter((letter: ILetterType) => this.isLetterHasText(searchText, letter))
+ );
+ if (to === size) {
+ this.setState({
+ filteredLetters: newLetters,
+ worker: null,
+ searchFor: searchText,
+ isSearch: false
+ });
+ } else {
+ const worker = setTimeout(() => lambdaWorker(newLetters, to, size));
+ this.setState({
+ filteredLetters: newLetters,
+ worker,
+ searchFor: searchText
+ });
+ }
+ };
+ lambdaWorker([], 0, this.state.letters.length);
+ }, 500),
+ isSearch: true
+ };
+ });
+ } else {
+ this.setState((state: IMyState) => {
+ if (state.worker) {
+ clearTimeout(state.worker);
+ }
+ return {
+ filteredLetters: null,
+ worker: null,
+ searchFor: searchText,
+ isSearch: false
+ };
+ });
+ }
+ }
+ const letters = this.state.filteredLetters || this.state.letters;
+ return (
+
+
+
+
+
+ );
+ }
+}
diff --git a/src/app/scripts/scripts.ts b/src/app/scripts/scripts.ts
new file mode 100644
index 0000000..59dc73d
--- /dev/null
+++ b/src/app/scripts/scripts.ts
@@ -0,0 +1,390 @@
+import { ILetterType } from '../types/types';
+
+export const MAX_LETTERS = 30;
+
+const icon1: string = require('../../images/icons/1.png');
+const icon2: string = require('../../images/icons/2.png');
+const icon3: string = require('../../images/icons/3.jpg');
+const icon4: string = require('../../images/icons/4.jpg');
+const icon5: string = require('../../images/icons/5.png');
+const icon6: string = require('../../images/icons/6.png');
+const icon7: string = require('../../images/icons/7.png');
+const icon8: string = require('../../images/icons/8.png');
+const icon9: string = require('../../images/icons/9.png');
+const icon10: string = require('../../images/icons/9.jpg');
+
+const phrases: string[] = [
+ 'практика показывает, что',
+ 'структура организации',
+ 'одна маленькая строчка',
+ 'реторический вопрос',
+ 'текст продолжил',
+ 'свой путь',
+ 'непостижимые разновидности',
+ 'я совсем один',
+ 'так счастлив',
+ 'мой друг',
+ 'вдохнуть в рисунок',
+ 'выразить',
+ 'прильнув к земле',
+ 'не под силу',
+ 'после',
+ 'сердечным отношение',
+ 'подумал он',
+ 'нерегулярным питанием',
+ 'осмелился',
+ 'красный',
+ 'синий',
+ 'вверху',
+ 'живота',
+ 'с высоты',
+ 'с чистого листа',
+ 'хочешь я',
+ 'в глазва',
+ 'взгляну в',
+ 'твои глаза',
+ 'и слова',
+ 'припомню все',
+ 'и снова повторю',
+ 'кто тебе сказал',
+ 'ну',
+ 'кто тебе сказал',
+ 'кто придумал',
+ 'что тебя',
+ 'я',
+ 'не',
+ 'люблю',
+ 'я каждый жест',
+ 'каждый',
+ 'взгляд твой',
+ 'в душе берегу',
+ 'твой голос',
+ 'в серде моем',
+ 'звуччит звеня',
+ 'нет',
+ 'никогда',
+ 'я тебя',
+ 'разлюбить не смогу',
+ 'и',
+ 'ты люби',
+ 'ты всегда',
+ 'люби меня',
+ 'ты решилва',
+ 'все',
+ 'что было',
+ 'больше не вернешь',
+ 'сердце пусто',
+ 'вместо чувства',
+ 'в нем осталась',
+ 'ложь',
+ 'и казалось',
+ 'не осталось',
+ 'верного пути',
+ 'выбор сделан',
+ 'ты б хотела',
+ 'навсегда',
+ 'уйти',
+ 'навсегда уйти',
+ 'навсегда уйти',
+ 'и с чистого листа',
+ 'опять',
+ 'начнешь сначала',
+ 'звоню в',
+ 'последний раз',
+ 'а',
+ 'голос мой',
+ 'сотри',
+ 'и с чистого',
+ 'листа',
+ 'и снова все',
+ 'сначала',
+ 'закончилась',
+ 'про нас',
+ 'история любви',
+ 'история',
+ 'любви',
+ 'смелый',
+ 'как ветер',
+ 'свободный',
+ 'я делал все',
+ 'что душе угодно',
+ 'жил для себя',
+ 'год за годом',
+ 'крутой проявляя нрав',
+ 'сколько',
+ 'девченок хороших',
+ 'влюбилось в',
+ 'меня',
+ 'неосторожно',
+ 'всех сосчитать',
+ 'невозможно',
+ 'попробуй меня исправь',
+ 'и одна',
+ 'лишь ты',
+ 'много-много',
+ 'лет',
+ 'говорила нет',
+ 'ты одна',
+ 'ты такая',
+ 'я тебя знаю',
+ 'больше в мире таких',
+ 'таких не бывает',
+ 'все не то',
+ 'все не так',
+ 'ты мой друг',
+ 'я твой враг',
+ 'как же так',
+ 'все у нас',
+ 'с тоюой',
+ 'был',
+ 'апрель',
+ 'и в любви',
+ 'мы клялись',
+ 'но увы',
+ 'пролетел',
+ 'желтый лист',
+ 'по',
+ 'бульварам Москвы',
+ 'третье сентября',
+ 'день',
+ 'прощания',
+ 'день, когда',
+ 'горят',
+ 'костры рябин',
+ 'как костры горят',
+ 'обещяния',
+ 'день когда',
+ 'я совсем один',
+ 'я календарь',
+ 'переверну',
+ 'и снова третье сенятбря',
+ 'на фото я',
+ 'твое взгляну и',
+ 'снова тертье сентября',
+ 'но почему',
+ 'но почему',
+ 'расстаться все же',
+ 'нам пришлось',
+ 'но былов все',
+ 'у нас',
+ 'всерьез',
+ 'второго сентября'
+];
+
+const names: string[] = [
+ 'Милослав',
+ 'Федосий',
+ 'Александр',
+ 'Самойло',
+ 'Кирьяк',
+ 'Фофан',
+ 'Аверьян',
+ 'Пантелеимон',
+ 'Игнат',
+ 'Прокопий',
+ 'Лев',
+ 'Лукьян',
+ 'Данила',
+ 'Филимон',
+ 'Акиндин',
+ 'Егор',
+ 'Панкратий',
+ 'Роман',
+ 'Абакум',
+ 'Мартын',
+ 'Еремей',
+ 'Гаврило',
+ 'Андрон',
+ 'Нафанаил',
+ 'Гаврила',
+ 'Федосий',
+ 'Прокофий',
+ 'Ипатий',
+ 'Аврамий',
+ 'Артемий',
+ 'Вавила',
+ 'Харлам',
+ 'Давыд',
+ 'Мордва'
+];
+
+const surnames: string[] = [
+ 'Разумовский',
+ 'Вревский',
+ 'Ромодановский',
+ 'Скавронский',
+ 'Ржевский',
+ 'Урусов',
+ 'Хилков',
+ 'Татищев',
+ 'Нарышкин',
+ 'Бакаев',
+ 'Мещерский',
+ 'Херасков',
+ 'Шаховской',
+ 'Гершфельд',
+ 'Рабин',
+ 'Менакер',
+ 'Фукс',
+ 'Оппенгеймер',
+ 'Богораз',
+ 'Вольф',
+ 'Краузе',
+ 'Беккер',
+ 'Арендт',
+ 'Вагнер',
+ 'Гагин',
+ 'Корсак',
+ 'Сверчков',
+ 'Мухин',
+ 'Нигматуллин',
+ 'Беклемишев',
+ 'Великая'
+];
+
+const icons: string[] = [icon1, icon2, icon3, icon4, icon5, icon6, icon7, icon8, icon9, icon10];
+
+const months: string[] = [
+ 'янв',
+ 'фев',
+ 'мар',
+ 'апр',
+ 'май',
+ 'июн',
+ 'июл',
+ 'авг',
+ 'сен',
+ 'окт',
+ 'ноя',
+ 'дек'
+];
+
+export function genText(): string[] {
+ const minParagraphCount = 1;
+ const maxParagraphCount = 5;
+
+ const minSentenceCount = 1;
+ const maxSentenceCount = 10;
+
+ const minPhraseCount = 2;
+ const maxPhraseCount = 20;
+
+ const paragraphCount: number = Math.floor(
+ Math.random() * (maxParagraphCount - minParagraphCount) + minParagraphCount
+ );
+
+ const html: string[] = [];
+ for (let i = 0; i < paragraphCount; i++) {
+ let string = '';
+ const sentenceCount: number = Math.floor(
+ Math.random() * (maxSentenceCount - minSentenceCount) + minSentenceCount
+ );
+ for (let j = 0; j < sentenceCount; j++) {
+ const phraseCount: number = Math.floor(
+ Math.random() * (maxPhraseCount - minPhraseCount) + minPhraseCount
+ );
+ for (let k = 0; k < phraseCount; k++) {
+ let phrase: string = phrases[Math.floor(Math.random() * (phrases.length - 1))];
+ if (k === 0) {
+ phrase = phrase.charAt(0).toUpperCase() + phrase.substr(1, phrase.length - 1);
+ }
+ string += `${phrase} `;
+ }
+ string = string.substr(0, string.length - 1);
+ string += '. ';
+ }
+ html.push(string);
+ }
+ return html;
+}
+
+export function genAuthorName(): string {
+ return `${surnames[Math.floor(Math.random() * (surnames.length - 1))]} ${
+ names[Math.floor(Math.random() * (names.length - 1))]
+ }`;
+}
+
+export function genAuthorImage(): string {
+ return icons[Math.floor(Math.random() * (icons.length - 1))];
+}
+
+export function genHeadText(): string {
+ const minPhraseCount = 2;
+ const maxPhraseCount = 20;
+
+ let string = '';
+
+ const phraseCount: number = Math.floor(
+ Math.random() * (maxPhraseCount - minPhraseCount) + minPhraseCount
+ );
+ for (let k = 0; k < phraseCount; k++) {
+ let phrase: string = phrases[Math.floor(Math.random() * (phrases.length - 1))];
+ if (k === 0) {
+ phrase = phrase.charAt(0).toUpperCase() + phrase.substr(1, phrase.length - 1);
+ }
+ string += `${phrase} `;
+ }
+ string = string.substr(0, string.length - 1);
+ string += '. ';
+
+ return string;
+}
+
+export const getHeadDate = (date: Date) => {
+ const month = date.getMonth();
+ const day = date.getDate();
+ return `${day} ${months[month]}`;
+};
+
+const addZero = (x: number) => {
+ if (x < 10) {
+ return `0${x}`;
+ }
+ return x.toString();
+};
+
+export const getDate = (date: Date) => {
+ const year = date.getFullYear();
+ const month = date.getMonth();
+ const day = date.getDate();
+ const hours = date.getHours();
+ const minutes = date.getMinutes();
+ return `${year}-${addZero(month)}-${addZero(day)} ${addZero(hours)}:${addZero(minutes)}`;
+};
+
+export const generateLetters = () => {
+ let letters: ILetterType[] = [];
+ const checked: { [id: string]: boolean } = {};
+
+ for (let i = 0; i < 10000; i++) {
+ const id = `id${i}`;
+ const authorName: string = genAuthorName();
+ const authorImage: string = genAuthorImage();
+ const headText: string = genHeadText();
+ const letterText: string[] = genText();
+
+ const date: Date = new Date();
+ const headTagDate: string = getDate(date);
+ const headDate: string = getHeadDate(date);
+ checked[id] = false;
+
+ const newLetter: ILetterType = {
+ id,
+ letterText,
+ authorName,
+ authorImage,
+ headText,
+ isChecked: false,
+ isVisible: true,
+ isRead: true,
+ addAnimation: true,
+ deleteAnimation: false,
+ headTagDate,
+ headDate
+ };
+
+ letters = [newLetter].concat(letters);
+ }
+
+ return { letters, checked };
+};
diff --git a/src/app/types/types.ts b/src/app/types/types.ts
new file mode 100644
index 0000000..ca6fba2
--- /dev/null
+++ b/src/app/types/types.ts
@@ -0,0 +1,14 @@
+export interface ILetterType {
+ id: string;
+ letterText: string[];
+ authorName: string;
+ authorImage: string;
+ headText: string;
+ isChecked: boolean;
+ isVisible: boolean;
+ isRead: boolean;
+ addAnimation: boolean;
+ deleteAnimation: boolean;
+ headTagDate: string;
+ headDate: string;
+}
diff --git a/src/fonts/HelveticaNeueCyr-Bold.ttf b/src/fonts/HelveticaNeueCyr-Bold.ttf
new file mode 100644
index 0000000..8dcfee9
Binary files /dev/null and b/src/fonts/HelveticaNeueCyr-Bold.ttf differ
diff --git a/src/fonts/HelveticaNeueCyr-Light.otf b/src/fonts/HelveticaNeueCyr-Light.otf
new file mode 100644
index 0000000..7ced49a
Binary files /dev/null and b/src/fonts/HelveticaNeueCyr-Light.otf differ
diff --git a/src/fonts/HelveticaNeueCyr-Medium.otf b/src/fonts/HelveticaNeueCyr-Medium.otf
new file mode 100644
index 0000000..a2cde16
Binary files /dev/null and b/src/fonts/HelveticaNeueCyr-Medium.otf differ
diff --git a/src/fonts/YandexSansText-Regular.eot b/src/fonts/YandexSansText-Regular.eot
new file mode 100644
index 0000000..ce7d907
Binary files /dev/null and b/src/fonts/YandexSansText-Regular.eot differ
diff --git a/src/images/check.png b/src/images/check.png
new file mode 100644
index 0000000..0bd2b77
Binary files /dev/null and b/src/images/check.png differ
diff --git a/src/images/cross-symbol-dark.png b/src/images/cross-symbol-dark.png
new file mode 100644
index 0000000..c3d85d3
Binary files /dev/null and b/src/images/cross-symbol-dark.png differ
diff --git a/src/images/cross-symbol.png b/src/images/cross-symbol.png
new file mode 100644
index 0000000..7765268
Binary files /dev/null and b/src/images/cross-symbol.png differ
diff --git a/src/images/header-mail-dark.svg b/src/images/header-mail-dark.svg
new file mode 100644
index 0000000..5237141
--- /dev/null
+++ b/src/images/header-mail-dark.svg
@@ -0,0 +1,66 @@
+
+
diff --git a/src/images/header-mail.svg b/src/images/header-mail.svg
new file mode 100644
index 0000000..fc9da9f
--- /dev/null
+++ b/src/images/header-mail.svg
@@ -0,0 +1,66 @@
+
+
diff --git a/src/images/header-yandex-dark.svg b/src/images/header-yandex-dark.svg
new file mode 100644
index 0000000..daee62c
--- /dev/null
+++ b/src/images/header-yandex-dark.svg
@@ -0,0 +1,79 @@
+
+
diff --git a/src/images/header-yandex.svg b/src/images/header-yandex.svg
new file mode 100644
index 0000000..69e2032
--- /dev/null
+++ b/src/images/header-yandex.svg
@@ -0,0 +1,79 @@
+
+
diff --git a/src/images/icons/1.png b/src/images/icons/1.png
new file mode 100644
index 0000000..b54a914
Binary files /dev/null and b/src/images/icons/1.png differ
diff --git a/src/images/icons/2.png b/src/images/icons/2.png
new file mode 100644
index 0000000..51225ff
Binary files /dev/null and b/src/images/icons/2.png differ
diff --git a/src/images/icons/3.jpg b/src/images/icons/3.jpg
new file mode 100644
index 0000000..108a275
Binary files /dev/null and b/src/images/icons/3.jpg differ
diff --git a/src/images/icons/4.jpg b/src/images/icons/4.jpg
new file mode 100644
index 0000000..d7a982f
Binary files /dev/null and b/src/images/icons/4.jpg differ
diff --git a/src/images/icons/5.png b/src/images/icons/5.png
new file mode 100644
index 0000000..5db4d16
Binary files /dev/null and b/src/images/icons/5.png differ
diff --git a/src/images/icons/6.png b/src/images/icons/6.png
new file mode 100644
index 0000000..e3687ee
Binary files /dev/null and b/src/images/icons/6.png differ
diff --git a/src/images/icons/7.png b/src/images/icons/7.png
new file mode 100644
index 0000000..a53067c
Binary files /dev/null and b/src/images/icons/7.png differ
diff --git a/src/images/icons/8.png b/src/images/icons/8.png
new file mode 100644
index 0000000..1d1805f
Binary files /dev/null and b/src/images/icons/8.png differ
diff --git a/src/images/icons/9.jpg b/src/images/icons/9.jpg
new file mode 100644
index 0000000..b33117d
Binary files /dev/null and b/src/images/icons/9.jpg differ
diff --git a/src/images/icons/9.png b/src/images/icons/9.png
new file mode 100644
index 0000000..0ab5d21
Binary files /dev/null and b/src/images/icons/9.png differ
diff --git a/src/images/icons/check.png b/src/images/icons/check.png
new file mode 100644
index 0000000..0bd2b77
Binary files /dev/null and b/src/images/icons/check.png differ
diff --git a/src/images/itmo.png b/src/images/itmo.png
new file mode 100644
index 0000000..73a861f
Binary files /dev/null and b/src/images/itmo.png differ
diff --git a/src/images/ox.png b/src/images/ox.png
new file mode 100644
index 0000000..c7b5da9
Binary files /dev/null and b/src/images/ox.png differ
diff --git a/src/images/yandex.png b/src/images/yandex.png
new file mode 100644
index 0000000..986765f
Binary files /dev/null and b/src/images/yandex.png differ
diff --git a/src/index.css b/src/index.css
index 2b6e525..097218d 100644
--- a/src/index.css
+++ b/src/index.css
@@ -1,10 +1,30 @@
+@font-face {
+ font-family: Yandex Sans;
+ font-style: normal;
+ src: url(./fonts/YandexSansText-Regular.eot) format('eot');
+}
+
+@font-face {
+ font-family: HelveticaNeue;
+ src: url(./fonts/HelveticaNeueCyr-Light.otf) format('opentype');
+}
+
+@font-face {
+ font-family: HelveticaNeue;
+ font-style: normal;
+ font-weight: 500;
+ src: url(./fonts/HelveticaNeueCyr-Medium.otf) format('opentype');
+}
+
+@font-face {
+ font-family: HelveticaNeue;
+ font-style: normal;
+ font-weight: bold;
+ src: url(./fonts/HelveticaNeueCyr-Bold.ttf) format('truetype');
+}
+
body {
- padding: 0;
- margin: 0;
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',
- 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
+ background-color: black;
}
code {
diff --git a/src/index.jsx b/src/index.tsx
similarity index 61%
rename from src/index.jsx
rename to src/index.tsx
index ffc72ee..3e29557 100644
--- a/src/index.jsx
+++ b/src/index.tsx
@@ -1,5 +1,5 @@
-import React from 'react';
-import ReactDOM from 'react-dom';
+import * as React from 'react';
+import * as ReactDOM from 'react-dom';
import { App } from './app';
diff --git a/src/react-app-env.d.ts b/src/react-app-env.d.ts
new file mode 100644
index 0000000..6431bc5
--- /dev/null
+++ b/src/react-app-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..77c567c
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,25 @@
+{
+ "compilerOptions": {
+ "target": "es6",
+ "lib": [
+ "dom",
+ "dom.iterable",
+ "esnext"
+ ],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "module": "esnext",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "preserve"
+ },
+ "include": [
+ "src"
+ ]
+}