├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ ├── gh-pages.yml │ └── sanity-check.yml ├── .gitignore ├── .husky └── pre-commit ├── .nojekyll ├── .reveal-md ├── listing.html └── styles.css ├── .yaspellerrc.js ├── README.md ├── lessons ├── final-task │ ├── ht.md │ └── lecture.md ├── lesson01 │ ├── code │ │ └── fizzbuzz │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ ├── sandbox.config.json │ │ │ └── src │ │ │ ├── ..js │ │ │ ├── index.js │ │ │ └── index.test.js │ ├── ht.md │ ├── images │ │ ├── question1.png │ │ ├── question2.png │ │ ├── question3.png │ │ ├── question4.png │ │ ├── question5.png │ │ ├── question6.png │ │ └── question7.png │ └── lecture.md ├── lesson02 │ ├── code │ │ └── isPalindrom │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ └── src │ │ │ ├── isPalindrom.js │ │ │ └── isPalindrom.test.js │ └── lecture.md ├── lesson03 │ ├── 1.lecture.md │ ├── 2.lecture.md │ ├── 3.lecture.md │ ├── code │ │ ├── bind │ │ │ ├── .codesandbox │ │ │ │ └── workspace.json │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ └── src │ │ │ │ ├── bind.js │ │ │ │ ├── bind.test.js │ │ │ │ └── index.js │ │ ├── singleton │ │ │ ├── .codesandbox │ │ │ │ └── workspace.json │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ └── src │ │ │ │ ├── index.js │ │ │ │ ├── makeSingleton.js │ │ │ │ ├── makeSingleton.test.js │ │ │ │ ├── singleton.js │ │ │ │ ├── singleton.test.js │ │ │ │ └── styles.css │ │ └── smart-getter │ │ │ ├── .codesandbox │ │ │ └── workspace.json │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ └── src │ │ │ ├── get.js │ │ │ ├── get.test.js │ │ │ └── index.js │ ├── homework_lec1.md │ └── homework_lec3.md ├── lesson04 │ ├── 01.md │ ├── 02.md │ └── code │ │ ├── DOMExamples │ │ ├── index.html │ │ ├── package.json │ │ └── src │ │ │ └── script.js │ │ ├── DOMExamplesPractice │ │ ├── index.html │ │ ├── package.json │ │ └── src │ │ │ └── script.js │ │ ├── dataStoragePractice │ │ ├── index.html │ │ ├── package.json │ │ └── src │ │ │ └── script.js │ │ ├── fetchPractice │ │ ├── index.html │ │ ├── package.json │ │ └── src │ │ │ └── script.js │ │ └── singleThreadInJs │ │ ├── index.html │ │ ├── package.json │ │ ├── sandbox.config.json │ │ └── src │ │ └── script.js ├── lesson05 │ ├── code │ │ ├── isEqualXAndO │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ └── src │ │ │ │ ├── isEqualXAndO.js │ │ │ │ └── isEqualXAndO.test.js │ │ └── uicalc │ │ │ ├── .gitignore │ │ │ ├── babel.config.js │ │ │ ├── index.html │ │ │ ├── jest.config.js │ │ │ ├── package.json │ │ │ └── src │ │ │ └── index.js │ ├── homework.md │ ├── homework2.md │ └── lecture.md ├── lesson06 │ ├── homework.md │ ├── images │ │ └── git_show.png │ └── lecture.md ├── lesson07 │ ├── ht.md │ └── lecture.md ├── lesson08 │ └── lecture.md ├── lesson09 │ ├── homework.md │ └── lecture.md ├── lesson10 │ ├── images │ │ └── callbackhell.png │ └── lecture.md ├── lesson13 │ ├── ht.md │ └── lecture.md ├── lesson19 │ ├── ht.md │ └── lecture.md ├── lesson20 │ ├── code │ │ └── notes-list │ │ │ ├── .codesandbox │ │ │ └── workspace.json │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ └── src │ │ │ ├── addForm.js │ │ │ ├── addForm.test.js │ │ │ ├── index.js │ │ │ └── styles.css │ ├── ht.md │ └── lecture.md ├── lesson21 │ ├── code │ │ ├── .codesandbox │ │ │ └── workspace.json │ │ ├── index.html │ │ ├── package.json │ │ └── src │ │ │ ├── Game.test.ts │ │ │ ├── Game.ts │ │ │ ├── GameField.test.ts │ │ │ ├── GameField.ts │ │ │ ├── GameView.test.ts │ │ │ ├── GameView.ts │ │ │ ├── index.ts │ │ │ ├── styles.css │ │ │ └── types │ │ │ └── Cell.ts │ ├── images │ │ └── slicing.png │ └── lecture.md ├── lesson22 │ ├── code │ │ ├── compose │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ ├── sandbox.config.json │ │ │ └── src │ │ │ │ ├── index.js │ │ │ │ └── index.test.js │ │ └── memo │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ ├── sandbox.config.json │ │ │ └── src │ │ │ ├── ..js │ │ │ ├── index.js │ │ │ └── index.test.js │ ├── ht.md │ └── lecture.md ├── lesson23 │ └── lecture.md ├── lesson24 │ ├── ht.md │ └── lecture.md ├── lesson25 │ ├── code │ │ ├── index.html │ │ ├── package.json │ │ ├── sandbox.config.json │ │ ├── src │ │ │ ├── ToDoListController.ts │ │ │ ├── ToDoListModel.ts │ │ │ ├── ToDoListView.ts │ │ │ ├── components │ │ │ │ ├── Form.ts │ │ │ │ ├── List.ts │ │ │ │ └── ListItem.ts │ │ │ └── index.ts │ │ ├── style │ │ │ └── index.css │ │ └── tsconfig.json │ ├── images │ │ ├── MKC.jpg │ │ ├── MKC_components.jpg │ │ ├── MVC-Process.png │ │ ├── ModelViewControllerDiagram.png │ │ └── todolist.png │ └── lecture.md ├── lesson26 │ ├── images │ │ └── mrbean.jpg │ └── lecture.md ├── lesson27 │ └── lecture.md ├── lesson28 │ ├── ht.md │ ├── ht2.md │ └── lecture.md ├── lesson31 │ ├── code │ │ └── code │ │ │ └── templatePractice │ │ │ ├── .codesandbox │ │ │ └── workspace.json │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ ├── src │ │ │ ├── index.ts │ │ │ ├── template.test.ts │ │ │ └── template.ts │ │ │ └── tsconfig.json │ └── lecture.md ├── lesson33 │ ├── code │ │ └── eventEmitter │ │ │ ├── .codesandbox │ │ │ └── workspace.json │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ ├── src │ │ │ ├── EventEmitter.test.ts │ │ │ ├── EventEmitter.ts │ │ │ └── index.ts │ │ │ └── tsconfig.json │ ├── homework.md │ ├── images │ │ ├── EventBus.jpeg │ │ └── ObservableUML.png │ └── lecture.md ├── lesson34 │ ├── code │ │ ├── combineReducers │ │ │ ├── .codesandbox │ │ │ │ └── workspace.json │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ ├── src │ │ │ │ ├── combineReducers.test.ts │ │ │ │ ├── combineReducers.ts │ │ │ │ └── index.ts │ │ │ └── tsconfig.json │ │ └── reduxBasic │ │ │ ├── .codesandbox │ │ │ └── workspace.json │ │ │ ├── babel.config.js │ │ │ ├── index.html │ │ │ ├── package-lock.json │ │ │ ├── package.json │ │ │ ├── src │ │ │ ├── configureStore.test.ts │ │ │ ├── configureStore.ts │ │ │ └── index.ts │ │ │ └── tsconfig.json │ ├── homework.md │ ├── images │ │ ├── EventBus.jpeg │ │ ├── middlewares.gif │ │ └── redux-data-flow.png │ └── lecture.md ├── lesson35 │ ├── code │ │ ├── client.js │ │ ├── index.html │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── server.ts │ │ ├── style.css │ │ ├── tsconfig.json │ │ └── types.d.ts │ ├── images │ │ ├── Archive_repo.png │ │ ├── REST_CRUD_operations.png │ │ ├── comparision1.png │ │ ├── comparision2.png │ │ ├── oauth_example1.png │ │ ├── oauth_example2.png │ │ ├── oauth_flow.png │ │ ├── requestScope.png │ │ ├── slack_RPC.png │ │ ├── slack_conversation_API.png │ │ ├── ta.png │ │ ├── ta1.png │ │ └── tripAdvisor.png │ └── lecture.md ├── lesson36 │ └── lecture.md ├── lesson37 │ └── lecture.md ├── lesson38 │ ├── code │ │ └── reduxDataLoading │ │ │ ├── .codesandbox │ │ │ └── workspace.json │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ ├── src │ │ │ ├── actions.ts │ │ │ ├── api.ts │ │ │ ├── index.ts │ │ │ ├── logger.ts │ │ │ ├── reducer.ts │ │ │ └── store.ts │ │ │ └── tsconfig.json │ ├── homework.md │ ├── images │ │ └── middlewares.gif │ └── lecture.md ├── lesson39 │ ├── code │ │ └── routing-examples │ │ │ ├── README.md │ │ │ ├── examples │ │ │ ├── hash-api.js │ │ │ ├── history-api.js │ │ │ ├── practice.js │ │ │ └── router.js │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ └── sandbox.config.json │ ├── hw.md │ └── lecture.md ├── lesson40 │ ├── code │ │ ├── .github │ │ │ └── workflows │ │ │ │ └── build.yml │ │ ├── .gitignore │ │ ├── .husky │ │ │ ├── .gitignore │ │ │ └── pre-commit │ │ ├── README.md │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── public │ │ │ └── index.html │ │ ├── src │ │ │ └── index.ts │ │ ├── tsconfig.json │ │ └── webpack.config.js │ ├── hw.md │ └── lecture.md ├── lesson42 │ ├── hw.md │ ├── images │ │ ├── calendar.png │ │ └── folder-structure.png │ └── lecture.md ├── lesson43 │ ├── hw.md │ ├── hw2.md │ ├── images │ │ ├── createElement.png │ │ ├── dom-reconciliation.webp │ │ └── vdom.png │ └── lecture.md ├── lesson44 │ ├── code │ │ ├── controlled-counter │ │ │ ├── .codesandbox │ │ │ │ └── workspace.json │ │ │ ├── package.json │ │ │ ├── public │ │ │ │ └── index.html │ │ │ ├── src │ │ │ │ ├── Counter.tsx │ │ │ │ ├── CounterButton.tsx │ │ │ │ └── index.tsx │ │ │ └── tsconfig.json │ │ └── counter-button │ │ │ ├── .codesandbox │ │ │ └── workspace.json │ │ │ ├── package.json │ │ │ ├── public │ │ │ └── index.html │ │ │ ├── src │ │ │ ├── CounterButton.test.tsx │ │ │ ├── CounterButton.tsx │ │ │ └── index.tsx │ │ │ └── tsconfig.json │ ├── images │ │ └── unidirectional-data-flow.png │ └── lecture.md ├── lesson45 │ ├── images │ │ ├── tree-state.png │ │ └── tree.png │ └── lecture.md ├── lesson46 │ ├── code │ │ └── reactredux │ │ │ ├── .codesandbox │ │ │ └── workspace.json │ │ │ ├── package.json │ │ │ ├── public │ │ │ └── index.html │ │ │ ├── src │ │ │ ├── 1_pureReact │ │ │ │ └── index.tsx │ │ │ ├── 2_withHOC │ │ │ │ ├── index.tsx │ │ │ │ └── withRedux.tsx │ │ │ ├── 3_react-redux │ │ │ │ ├── Counter.tsx │ │ │ │ └── index.tsx │ │ │ ├── index.tsx │ │ │ ├── store │ │ │ │ ├── actionsCounter.ts │ │ │ │ ├── counter.ts │ │ │ │ └── index.ts │ │ │ └── styles.css │ │ │ └── tsconfig.json │ └── lecture.md ├── lesson47 │ ├── homework.md │ └── lecture.md ├── lesson48 │ ├── homework.md │ └── lecture.md ├── lesson49 │ ├── code │ │ ├── assets │ │ │ ├── read.txt │ │ │ ├── song.mp3 │ │ │ ├── tmp.json │ │ │ └── tmp.txt │ │ ├── commander │ │ │ ├── cli.ts │ │ │ ├── package-lock.json │ │ │ ├── package.json │ │ │ └── tsconfig.json │ │ ├── examples │ │ │ ├── 1-writeFileSync.ts │ │ │ ├── 2-readFileSync.ts │ │ │ ├── 3-readWriteFileCb.ts │ │ │ ├── 4-fsWatch.ts │ │ │ ├── 5-fsStat.ts │ │ │ ├── 6-dirRead.ts │ │ │ ├── 7-openAndReadFile.ts │ │ │ ├── 8-fsWriteStream.ts │ │ │ ├── 9-fsReadStream.ts │ │ │ └── os.ts │ │ └── inquirer │ │ │ ├── cli.ts │ │ │ ├── configs │ │ │ ├── tsconfig.node.json │ │ │ ├── tsconfig.react.json │ │ │ └── tsconfig.recommended.json │ │ │ ├── package-lock.json │ │ │ ├── package.json │ │ │ └── tsconfig.json │ ├── images │ │ ├── arch.png │ │ └── nodejs.png │ └── lecture.md ├── lesson50 │ ├── README.md │ ├── homework.md │ ├── images │ │ ├── bob_martin.jpg │ │ ├── exorcism.gif │ │ ├── jsguy.gif │ │ ├── polnomochiya.jpg │ │ └── ron_jeffries.jpg │ └── lecture.md ├── lesson51 │ ├── images │ │ ├── http.png │ │ ├── middleware.jpg │ │ └── osi.jpeg │ └── lecture.md └── lesson52 │ ├── code │ ├── ls.js │ ├── package.json │ ├── practice │ │ ├── ls.js │ │ └── server.js │ └── sample.js │ ├── hw1.md │ ├── hw2.md │ └── lecture.md ├── package.json ├── plugins.js ├── plugins └── tableofcontents.js ├── preparation └── tasks.md ├── reveal-md.json ├── reveal.json └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | /node_modules -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: "@typescript-eslint/parser", 3 | extends: ["plugin:react/recommended", "plugin:markdown/recommended"], 4 | plugins: ["@typescript-eslint", "react", "markdown"], 5 | overrides: [ 6 | { 7 | files: ["**/*.md"], 8 | processor: "markdown/markdown", 9 | }, 10 | { 11 | files: ["**/*.md/*.js"], 12 | rules: { 13 | "comma-dangle": ["error", "only-multiline"], 14 | }, 15 | }, 16 | ], 17 | settings: { 18 | react: { 19 | version: "17.0.2", 20 | }, 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /.github/workflows/gh-pages.yml: -------------------------------------------------------------------------------- 1 | # Simple workflow for deploying static content to GitHub Pages 2 | name: Deploy content to Pages 3 | 4 | on: 5 | # Runs on pushes targeting the default branch 6 | push: 7 | branches: ["master"] 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 13 | permissions: 14 | contents: read 15 | pages: write 16 | id-token: write 17 | 18 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 19 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 20 | concurrency: 21 | group: "pages" 22 | cancel-in-progress: false 23 | 24 | jobs: 25 | # Single deploy job since we're just deploying 26 | deploy: 27 | environment: 28 | name: github-pages 29 | url: ${{ steps.deployment.outputs.page_url }} 30 | runs-on: ubuntu-latest 31 | steps: 32 | - name: Checkout 33 | uses: actions/checkout@v3 34 | 35 | - name: Install Packages 36 | run: | 37 | yarn install 38 | 39 | - name: Build 40 | run: | 41 | npx reveal-md lessons/ --static dist --static-dirs=plugins 42 | rsync -a -r lessons/ dist/ 43 | cp -r plugins dist/_assets/ 44 | cp -r .reveal-md dist/_assets/ 45 | 46 | - name: Setup Pages 47 | uses: actions/configure-pages@v3 48 | - name: Upload artifact 49 | uses: actions/upload-pages-artifact@v2 50 | with: 51 | # Upload entire repository 52 | path: "dist" 53 | - name: Deploy to GitHub Pages 54 | id: deployment 55 | uses: actions/deploy-pages@v2 56 | -------------------------------------------------------------------------------- /.github/workflows/sanity-check.yml: -------------------------------------------------------------------------------- 1 | name: PR Sanity Check 2 | 3 | on: pull_request 4 | 5 | jobs: 6 | lint: 7 | runs-on: ubuntu-20.04 8 | steps: 9 | - name: Checkout 10 | uses: actions/checkout@v2 11 | 12 | - name: Install Packages 13 | run: | 14 | yarn install 15 | - name: Lint 16 | run: | 17 | yarn lint 18 | - name: Build 19 | run: | 20 | npx reveal-md lessons/ --static dist 21 | rsync -a -r lessons/ dist/ 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .DS_Store 4 | .vscode 5 | .eslintcache 6 | tmp 7 | .idea 8 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged -------------------------------------------------------------------------------- /.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/.nojekyll -------------------------------------------------------------------------------- /.reveal-md/listing.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{pageTitle}} 6 | 7 | 8 | 9 | 10 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /.reveal-md/styles.css: -------------------------------------------------------------------------------- 1 | .reveal { 2 | font-size: 36px; 3 | } 4 | 5 | .reveal pre code { 6 | max-height: 80%; 7 | } 8 | 9 | * { 10 | font-family: Helvetica, sans-serif !important; 11 | } 12 | 13 | .table-of-contents h1 { 14 | font-size: 16px; 15 | } 16 | .table-of-contents { 17 | max-height: 95%; 18 | overflow-y: scroll; 19 | overflow: -moz-scrollbars-vertical; 20 | font-size: 14px; 21 | } 22 | 23 | .section[data-markdown-parsed="true"] i { 24 | font-style: italic; 25 | } 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JavaScript Developer. Basic 2 | 3 | Это репозиторий курса ["OTUS JavaScript Developer. Basic"](https://otus.ru/lessons/javascript-basic/) 4 | 5 |
6 | Структура репозитория 7 | 8 | Репозиторий имеет следующую структуру 9 | 10 | - в корне проекта находятся файлы настроек проекта и используемых инструментов 11 | - в директории `lessons` находится под-директории по маске `lesson{XX}` с материалами для каждого занятия 12 |
13 | 14 |
15 | Работа с репозиторием 16 | 17 | Репозиторий обслуживается инструментами на базе Node.js. Для работы понадобится `node.js` и `yarn` (в качестве пакетного менеджера). 18 | 19 | ```bash 20 | # Склонируйте репозиторий 21 | git clone https://github.com/vvscode/otus--javascript-basic 22 | 23 | # Установите зависимости 24 | cd otus--javascript-basic && yarn 25 | 26 | # Создайте директорию для нового занятия 27 | mkdir lessons/lessonXX 28 | 29 | # Создайте файл для нового занятия 30 | touch lessons/lessonXX/lecture.md 31 | 32 | # Запустите reveal-md в режиме разработки 33 | yarn dev lessons/lessonXX/lecture.md 34 | ``` 35 | 36 | При коммите должны сработать husky-хуки для проверки и форматирования измененных файлов. Дополнительная проверка настроена через github-actions. 37 | 38 | При мерже пуллреквеста в мастер автоматически произойдет деплой изменений на сервис gh-pages. Результат можно будет увидеть здесь [otus--javascript-basic](https://vvscode.github.io/otus--javascript-basic/index.html). 39 | 40 | Если для занятия (для демонстрации или для активностей) нужно запускать примеры кода - используйте codesandbox, разместив код в поддиректории соответствующего урока (чтобы держать все материалы в одном месте). 41 | 42 |
43 | -------------------------------------------------------------------------------- /lessons/final-task/ht.md: -------------------------------------------------------------------------------- 1 | --- 2 | Зачем: Выбрать/придумать проект, к которому вы будете применять получаемые знания. В идеале это решение какой-то задачи/проблемы, которая близка/интересна лично вам. В первую очередь это нужно для практического закрепления знаний/навыков, во вторую - для демонстрации этого другим людям. 3 | --- 4 | 5 | В личном кабинете, в уроке к заданию, вам нужно нажать кнопку "Предложить тему" и ввести название темы (именно в таком виде она пойдет в итоговый сертификат). 6 | 7 | После этого нужно отдельным сообщением описать предполагаемый функционал (чтобы было ясно, что именно вы планируете реализовать). 8 | -------------------------------------------------------------------------------- /lessons/final-task/lecture.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Выбор темы и организация проектной работы 3 | description: Выбор темы и организация проектной работы 4 | --- 5 | 6 | # OTUS 7 | 8 | ## Javascript Basic 9 | 10 | 11 | 12 | Вопросы? 13 | 14 | 15 | 16 | Требования к финальному проекту 17 | 18 | 19 | 20 | Обязательно все из списка: 21 | 22 | - поддержка пользовательских сессий / сохранения пользовательских данных 23 | - использование внешнего API 24 | - минимум 3 разных роута 25 | - использование параметризированных роутов 26 | - настроенные линтеры, тесты и автодеплой через CI/CD 27 | - возможность проверить приложение без локального запуска (с исключениями) 28 | - оформленный репозиторий и README 29 | 30 | - покрытие кода тестами от 70%\* 31 | - основной функционал покрыт интеграционными тестами (testing-library/cypress/testcafe/etc)\* 32 | 33 | 34 | 35 | Поддержка хотя бы одного пункта из списка: 36 | 37 | - аутентификация пользователя 38 | - отзывчивый дизайн 39 | - работа в оффлайн (PWA) / нативные технологии под мобильные платформы (React Native/cordova) 40 | - совместная работа пользователей 41 | 42 | 43 | 44 | Примеры: 45 | 46 | - крокодил c рисованием на канвасе 47 | - сеги 48 | - книга рецептов 49 | - расширение для браузера для подсветки и создания заметок 50 | - клиент для slack / tg / vk 51 | - базовая аркадная игра с преследованием 52 | 53 | 54 | 55 | Вопросы? 56 | -------------------------------------------------------------------------------- /lessons/lesson01/code/fizzbuzz/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Parcel Sandbox 5 | 6 | 7 | 8 | 9 |
10 | Напишите программу, которая выводит на экран числа от 1 до 100. 
11 | 
12 | При этом вместо чисел, 
13 | - кратных 3, программа должна выводить слово Fizz, 
14 | - а вместо чисел, кратных 5 — слово Buzz,
15 | - a число кратно 15, то программа должна выводить слово FizzBuzz
16 | 
17 | Для вывода данных использовать `console.log` (Например `console.log('FizzBuzz');`)
18 | 	
20 | Начало вывода: 21 |
    22 |
  1. 1
  2. 23 |
  3. 2
  4. 24 |
  5. Fizz
  6. 25 |
  7. 4
  8. 26 |
  9. Buzz
  10. 27 |
  11. Fizz
  12. 28 |
  13. 7
  14. 29 |
  15. 8
  16. 30 |
  17. Fizz
  18. 31 |
  19. Buzz
  20. 32 |
  21. 11
  22. 33 |
  23. Fizz
  24. 34 |
  25. 13
  26. 35 |
  27. 14
  28. 36 |
  29. FizzBuzz
  30. 37 |
  31. ...
  32. 38 |
39 | 40 | Краткая справка по конструкциям языка 43 | 44 |
45 | 46 | 50 | Краткие заметки в помощь 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /lessons/lesson01/code/fizzbuzz/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "otus-fizzbuzz", 3 | "version": "1.0.0", 4 | "description": "Basic fizzBuzz practice sandbox", 5 | "main": "index.html", 6 | "scripts": { 7 | "start": "parcel index.html --open", 8 | "build": "parcel build index.html" 9 | }, 10 | "dependencies": {}, 11 | "devDependencies": { 12 | "@babel/core": "7.2.0", 13 | "parcel-bundler": "^1.6.1" 14 | }, 15 | "keywords": [ 16 | "otus" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /lessons/lesson01/code/fizzbuzz/sandbox.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "infiniteLoopProtection": false, 3 | "hardReloadOnChange": true, 4 | "view": "browser" 5 | } 6 | -------------------------------------------------------------------------------- /lessons/lesson01/code/fizzbuzz/src/..js: -------------------------------------------------------------------------------- 1 | export function getCorrectCallsArgs() { 2 | return [ 3 | [1], 4 | [2], 5 | ["Fizz"], 6 | [4], 7 | ["Buzz"], 8 | ["Fizz"], 9 | [7], 10 | [8], 11 | ["Fizz"], 12 | ["Buzz"], 13 | [11], 14 | ["Fizz"], 15 | [13], 16 | [14], 17 | ["FizzBuzz"], 18 | [16], 19 | [17], 20 | ["Fizz"], 21 | [19], 22 | ["Buzz"], 23 | ["Fizz"], 24 | [22], 25 | [23], 26 | ["Fizz"], 27 | ["Buzz"], 28 | [26], 29 | ["Fizz"], 30 | [28], 31 | [29], 32 | ["FizzBuzz"], 33 | [31], 34 | [32], 35 | ["Fizz"], 36 | [34], 37 | ["Buzz"], 38 | ["Fizz"], 39 | [37], 40 | [38], 41 | ["Fizz"], 42 | ["Buzz"], 43 | [41], 44 | ["Fizz"], 45 | [43], 46 | [44], 47 | ["FizzBuzz"], 48 | [46], 49 | [47], 50 | ["Fizz"], 51 | [49], 52 | ["Buzz"], 53 | ["Fizz"], 54 | [52], 55 | [53], 56 | ["Fizz"], 57 | ["Buzz"], 58 | [56], 59 | ["Fizz"], 60 | [58], 61 | [59], 62 | ["FizzBuzz"], 63 | [61], 64 | [62], 65 | ["Fizz"], 66 | [64], 67 | ["Buzz"], 68 | ["Fizz"], 69 | [67], 70 | [68], 71 | ["Fizz"], 72 | ["Buzz"], 73 | [71], 74 | ["Fizz"], 75 | [73], 76 | [74], 77 | ["FizzBuzz"], 78 | [76], 79 | [77], 80 | ["Fizz"], 81 | [79], 82 | ["Buzz"], 83 | ["Fizz"], 84 | [82], 85 | [83], 86 | ["Fizz"], 87 | ["Buzz"], 88 | [86], 89 | ["Fizz"], 90 | [88], 91 | [89], 92 | ["FizzBuzz"], 93 | [91], 94 | [92], 95 | ["Fizz"], 96 | [94], 97 | ["Buzz"], 98 | ["Fizz"], 99 | [97], 100 | [98], 101 | ["Fizz"], 102 | ["Buzz"], 103 | ]; 104 | } 105 | 106 | export function getNormalizedSource(fn) { 107 | return Function.prototype.toString 108 | .apply(fn) 109 | .replace(/\s+/g, " ") 110 | .replace(/(\w)\s+\(/g, "$1(") 111 | .replace(/(\w)\s+{/g, "$1{") 112 | .replace(/}\s+(\w)/g, "}$1"); 113 | } 114 | -------------------------------------------------------------------------------- /lessons/lesson01/code/fizzbuzz/src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Напишите программу, которая выводит на экран числа от 1 до 100. 3 | * При этом вместо чисел, 4 | * - кратных 3, программа должна выводить слово Fizz, 5 | * - а вместо чисел, кратных 5 — слово Buzz, 6 | * - a число кратно 15, то программа должна выводить слово FizzBuzz 7 | * Для вывода данных использовать `console.log` (Например `console.log('FizzBuzz');`) 8 | * 9 | * Краткая справка по конструкциям языка: 10 | * https://javascript.ru/basic/syntax-switch-for 11 | * https://gist.github.com/vvscode/5b5eec5bb5270de3ac27a3e0298430d9 12 | */ 13 | export function fizzBuzz() { 14 | // ваш код пишите здесь 15 | console.log("FizzBuzz"); 16 | } 17 | 18 | fizzBuzz(); 19 | -------------------------------------------------------------------------------- /lessons/lesson01/code/fizzbuzz/src/index.test.js: -------------------------------------------------------------------------------- 1 | import { fizzBuzz } from "./index.js"; 2 | import { getCorrectCallsArgs, getNormalizedSource } from "./..js"; 3 | 4 | // Решить задачу без использования конструкций if/else 5 | const SHOULD_NOT_USE_IF = false; 6 | // Решить задачу без использования тернарного оператора 7 | const SHOULD_NOT_USE_THERNARY = false; 8 | // Решить задачу без использования логических && и || 9 | const SHOULD_NOT_USE_AND_OR = false; 10 | 11 | describe("fizzBuzz", () => { 12 | jest.spyOn(console, "log"); 13 | 14 | beforeEach(() => { 15 | jest.resetAllMocks(); 16 | }); 17 | 18 | it("is a function", () => expect(typeof fizzBuzz).toBe("function")); 19 | 20 | it("prints values using console.log", () => { 21 | fizzBuzz(); 22 | expect(console.log).toHaveBeenCalled(); 23 | }); 24 | 25 | it("prints exactly 100 items", () => { 26 | fizzBuzz(); 27 | expect(console.log).toHaveBeenCalledTimes(100); 28 | }); 29 | 30 | describe("proper results", () => { 31 | getCorrectCallsArgs().forEach((args, index) => 32 | it(`is expected to see ${args} at position #${index + 1}`, () => { 33 | fizzBuzz(); 34 | expect(console.log.mock.calls[index]).toEqual(args); 35 | }) 36 | ); 37 | }); 38 | 39 | SHOULD_NOT_USE_IF && 40 | describe("Should not use if/else constructions", () => { 41 | ["if", "else"].forEach((stopWord) => 42 | it(`should not use ${stopWord}`, () => { 43 | expect(getNormalizedSource(fizzBuzz).includes(stopWord)).toBe(false); 44 | }) 45 | ); 46 | }); 47 | 48 | SHOULD_NOT_USE_THERNARY && 49 | describe("Should not use thernary operator (? :)", () => { 50 | ["?", ":"].forEach((stopWord) => 51 | it(`should not use ${stopWord}`, () => { 52 | expect(getNormalizedSource(fizzBuzz).includes(stopWord)).toBe(false); 53 | }) 54 | ); 55 | }); 56 | 57 | SHOULD_NOT_USE_AND_OR && 58 | describe("Should not use logical operators &&/||", () => { 59 | ["&&", "||"].forEach((stopWord) => 60 | it(`should not use ${stopWord}`, () => { 61 | expect(getNormalizedSource(fizzBuzz).includes(stopWord)).toBe(false); 62 | }) 63 | ); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /lessons/lesson01/ht.md: -------------------------------------------------------------------------------- 1 | --- 2 | Зачем: Сформулировать свои ожидания и цели (в идеале по SMART), сразу получить обрантную связь относительно того, на сколько ожидания могут быть закрыты курсом. Познакомиться с продуктивным подходом к задаванию вопросов, чтобы общение по учебе было эффективным (это же применимо к рабочим контекстам). 3 | --- 4 | 5 | ## Знакомство 6 | 7 | > Сформулирует цели и задачи поступления на курс, опишет свои ожидания от обучения. Познакомится с правилами "Хорошего тона" для продуктивного общения на технические темы 8 | 9 | 1. Развернуто (от 1000 символов) написать о следующих вещах 10 | 11 | - цель поступления на курс 12 | - ожидания от курса 13 | - как вы поймете, что курс был полезен (по каким конкретным критериям) 14 | 15 | 2. Написать конспект/чеклист/план к действию по мотивам статьи https://www.opennet.ru/docs/RUS/smart_question/ 16 | 17 | И то и другое отправить в "Чат с преподавателем" 18 | 19 | **Важно!** 20 | Если у вас нет опыта в JS, приступите к самостоятельному изучению видеокурса ["Javascript-для начинающих разработчиков"](https://otus.ru/online/online-js/). Если у вас нет к нему доступа, пожалуйста, напишите запрос на help@otus.ru. Вам нужно освоить его до начала модуля "Базовый JavaScript. Работа с GIT и настройка окружения". 21 | 22 | ### Критерии приемки 23 | 24 | - Описаны цели - 1 балл 25 | - Описаны ожидания - 1 балл 26 | - Описаны критерии оценки пользы курса - 1 балл 27 | - Описания даны в четком формате ("сенсорно очевидные"), чтобы другой человек глядя на описание мог сказать, достигнуто/выполнено или нет - по 1 баллу за цели / ожидания / критерии 28 | - Описание развернутое (больше 1000 символов) - 1 балл 29 | 30 | - Написан конспект к статье - 1 балл 31 | - Конспект содержит от 5 пунктов - 1 балл 32 | -Задание сдано в срок - 1 балл 33 | 34 | **Задание принято** от 9 баллов 35 | -------------------------------------------------------------------------------- /lessons/lesson01/images/question1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson01/images/question1.png -------------------------------------------------------------------------------- /lessons/lesson01/images/question2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson01/images/question2.png -------------------------------------------------------------------------------- /lessons/lesson01/images/question3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson01/images/question3.png -------------------------------------------------------------------------------- /lessons/lesson01/images/question4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson01/images/question4.png -------------------------------------------------------------------------------- /lessons/lesson01/images/question5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson01/images/question5.png -------------------------------------------------------------------------------- /lessons/lesson01/images/question6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson01/images/question6.png -------------------------------------------------------------------------------- /lessons/lesson01/images/question7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson01/images/question7.png -------------------------------------------------------------------------------- /lessons/lesson02/code/isPalindrom/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | isPalindrom 8 | 9 | 10 |

isPalindrom

11 |

12 | Палиндром — в буквальном переводе перевертень (от греч. παλινδρομέω — 13 | возвращаться) — это слово или фраза, которые одинаково читаются по буквам 14 | или по словам как слева направо, так и справа налево. 15 |

16 |

17 | Напишите функцию isPalindrom в файле 18 | src/isPalindrom.js, которая будет принимать строку и 19 | возвращать результат работы на палиндром 20 |

21 |

Перейдите на вкладку Tests для проверки решения

22 | 23 | 24 | -------------------------------------------------------------------------------- /lessons/lesson02/code/isPalindrom/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ispalindrom", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.html", 6 | "scripts": { 7 | "start": "parcel index.html --open", 8 | "build": "parcel build index.html" 9 | }, 10 | "dependencies": {}, 11 | "devDependencies": { 12 | "@babel/core": "7.2.0", 13 | "parcel-bundler": "^1.6.1" 14 | }, 15 | "keywords": [] 16 | } 17 | -------------------------------------------------------------------------------- /lessons/lesson02/code/isPalindrom/src/isPalindrom.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Проверка строки на палиндром 3 | * https://ru.wikipedia.org/wiki/%D0%9F%D0%B0%D0%BB%D0%B8%D0%BD%D0%B4%D1%80%D0%BE%D0%BC 4 | * @param {string} str - строка для проверки на палиндром 5 | * @returns {boolean} 6 | */ 7 | export function isPalindrom(str) {} 8 | -------------------------------------------------------------------------------- /lessons/lesson02/code/isPalindrom/src/isPalindrom.test.js: -------------------------------------------------------------------------------- 1 | import { isPalindrom } from "./isPalindrom"; 2 | 3 | describe("isPalindrom", () => { 4 | // palindroms 5 | ["", "a", "aa", "aba", "aabaa", "abcba"].forEach((palindrom) => 6 | it(`detects "${palindrom}" as palindrom`, () => { 7 | expect(isPalindrom(palindrom)).toBe(true); 8 | }) 9 | ); 10 | 11 | // not palindroms 12 | ["ab", "aab", "aaba", "aaabaa", "abc", "aaaa "].forEach((notPalindrom) => 13 | it(`does not detect "${notPalindrom}" as palindrom`, () => { 14 | expect(isPalindrom(notPalindrom)).toBe(false); 15 | }) 16 | ); 17 | }); 18 | -------------------------------------------------------------------------------- /lessons/lesson03/code/bind/.codesandbox/workspace.json: -------------------------------------------------------------------------------- 1 | { 2 | "responsive-preview": { 3 | "Mobile": [320, 675], 4 | "Tablet": [1024, 765], 5 | "Desktop": [1400, 800], 6 | "Desktop HD": [1920, 1080] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /lessons/lesson03/code/bind/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Parcel Sandbox 5 | 6 | 7 | 8 | 9 | bind(function, object) 10 |

11 | Связывает функцию function с объектом object. Это значит, что каждый раз 12 | когда она будет вызвана, this будет указывать на object. 13 |

14 |
15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /lessons/lesson03/code/bind/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bind", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.html", 6 | "scripts": { 7 | "start": "parcel index.html --open", 8 | "build": "parcel build index.html" 9 | }, 10 | "dependencies": { 11 | "parcel-bundler": "^1.6.1" 12 | }, 13 | "devDependencies": { 14 | "@babel/core": "7.2.0" 15 | }, 16 | "resolutions": { 17 | "@babel/preset-env": "7.13.8" 18 | }, 19 | "keywords": [] 20 | } 21 | -------------------------------------------------------------------------------- /lessons/lesson03/code/bind/src/bind.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Создает фукнцию-обертку, с зафиксированным контекстом 3 | * @param {Function} fn 4 | * @param {any} context 5 | */ 6 | export function bind(fn) { 7 | // put your code here 8 | return fn; 9 | } 10 | -------------------------------------------------------------------------------- /lessons/lesson03/code/bind/src/bind.test.js: -------------------------------------------------------------------------------- 1 | import { bind } from "./bind"; 2 | 3 | describe("bind", () => { 4 | it("is a funciton", () => { 5 | expect(bind).toBeInstanceOf(Function); 6 | }); 7 | 8 | it("returns new function", () => { 9 | expect(bind(jest.fn())).toBeInstanceOf(Function); 10 | }); 11 | 12 | it("returns function which calls with fixed context", () => { 13 | const bob = { name: "Bob" }; 14 | const spy = jest.fn(); 15 | 16 | const bindedFunction = bind(function () { 17 | spy(this); 18 | }, bob); 19 | expect(spy).not.toHaveBeenCalled(); 20 | bindedFunction(); 21 | expect(spy).toHaveBeenCalledWith(bob); 22 | }); 23 | 24 | it("passes params to the function", () => { 25 | const bob = { name: "Bob" }; 26 | const spy = jest.fn(); 27 | const [a, b] = [Math.random(), Math.random()]; 28 | 29 | const bindedFunction = bind(spy, bob); 30 | expect(spy).not.toHaveBeenCalled(); 31 | bindedFunction(a, b); 32 | expect(spy).toHaveBeenCalledWith(a, b); 33 | }); 34 | 35 | it("returns function results", () => { 36 | const x = Math.random(); 37 | const spy = jest.fn(() => x); 38 | expect(bind(spy, null)()).toBe(x); 39 | }); 40 | 41 | it("does not use .bind method from Function", () => { 42 | const x = Math.random(); 43 | jest.spyOn(Function.prototype, "bind"); 44 | const spy = jest.fn(() => x); 45 | expect(bind(spy, null)()).toBe(x); 46 | expect(Function.prototype.bind).not.toHaveBeenCalled(); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /lessons/lesson03/code/bind/src/index.js: -------------------------------------------------------------------------------- 1 | import { bind } from "./bind"; 2 | 3 | const bob = { 4 | name: "Bob", 5 | }; 6 | 7 | function greet() { 8 | alert(`Hello from ${this.name}`); 9 | } 10 | 11 | const greetFromBob = bind(greet, bob); 12 | 13 | document.getElementById("app").innerHTML = ` 14 | 15 | `; 16 | 17 | document.querySelector("button").addEventListener("click", greetFromBob); 18 | -------------------------------------------------------------------------------- /lessons/lesson03/code/singleton/.codesandbox/workspace.json: -------------------------------------------------------------------------------- 1 | { 2 | "responsive-preview": { 3 | "Mobile": [320, 675], 4 | "Tablet": [1024, 765], 5 | "Desktop": [1400, 800], 6 | "Desktop HD": [1920, 1080] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /lessons/lesson03/code/singleton/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Parcel Sandbox 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /lessons/lesson03/code/singleton/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "singleton", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.html", 6 | "scripts": { 7 | "start": "parcel index.html --open", 8 | "build": "parcel build index.html" 9 | }, 10 | "dependencies": { 11 | "parcel-bundler": "^1.6.1" 12 | }, 13 | "devDependencies": { 14 | "@babel/core": "7.2.0" 15 | }, 16 | "resolutions": { 17 | "@babel/preset-env": "7.13.8" 18 | }, 19 | "keywords": [] 20 | } 21 | -------------------------------------------------------------------------------- /lessons/lesson03/code/singleton/src/index.js: -------------------------------------------------------------------------------- 1 | import "./styles.css"; 2 | 3 | document.getElementById("app").innerHTML = ` 4 |

5 | 6 | Одиночка (англ. Singleton) 7 | — порождающий шаблон проектирования, гарантирующий, что в однопоточном приложении будет единственный экземпляр некоторого класса, и предоставляющий глобальную точку доступа к этому экземпляру. 8 |

`; 9 | -------------------------------------------------------------------------------- /lessons/lesson03/code/singleton/src/makeSingleton.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns singleton version of passed class/constructor function 3 | * @param {Call|Constructor Function} cls 4 | */ 5 | // export it with name makeSingleton 6 | -------------------------------------------------------------------------------- /lessons/lesson03/code/singleton/src/makeSingleton.test.js: -------------------------------------------------------------------------------- 1 | import { makeSingleton } from "./makeSingleton"; 2 | 3 | describe.skip("makeSingleton", () => { 4 | it("is a function", () => { 5 | expect(makeSingleton).toBeInstanceOf(Function); 6 | }); 7 | 8 | it("returns a constructor for passed class", () => { 9 | class X {} 10 | let SingletonX = makeSingleton(X); 11 | expect(SingletonX).toBeInstanceOf(Function); 12 | expect(new SingletonX()).toBeInstanceOf(X); 13 | }); 14 | 15 | it("returns singlton version of passed class", () => { 16 | class X {} 17 | let SingletonX = makeSingleton(X); 18 | expect(new X()).not.toBe(new X()); 19 | expect(new SingletonX()).toBe(new SingletonX()); 20 | }); 21 | 22 | it("passes arguments to the constructor", () => { 23 | class Y { 24 | constructor(a, b) { 25 | this.a = a; 26 | this.b = b; 27 | } 28 | } 29 | let SingletonY = makeSingleton(Y); 30 | let a = Math.random(); 31 | let b = Math.random(); 32 | let y = new SingletonY(a, b); 33 | expect(y.a).toBe(a); 34 | expect(y.b).toBe(b); 35 | }); 36 | 37 | it("passes arguments to the constructor only on first instance", () => { 38 | function Z(a, b) { 39 | this.a = a; 40 | this.b = b; 41 | } 42 | let SingletonZ = makeSingleton(Z); 43 | let a = Math.random(); 44 | let b = Math.random(); 45 | let z1 = new SingletonZ(a, b); 46 | let z2 = new SingletonZ(1, 2); 47 | let z3 = new SingletonZ(3, 4); 48 | expect(z1.a).toBe(a); 49 | expect(z2.b).toBe(b); 50 | expect(z1).toBe(z2); 51 | expect(z1).toBe(z3); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /lessons/lesson03/code/singleton/src/singleton.js: -------------------------------------------------------------------------------- 1 | // Put your singleton here 2 | // export it with name Singleton 3 | -------------------------------------------------------------------------------- /lessons/lesson03/code/singleton/src/singleton.test.js: -------------------------------------------------------------------------------- 1 | import { Singleton } from "./singleton"; 2 | 3 | describe.skip("Singleton", () => { 4 | it("is a funciton", () => expect(Singleton).toBeInstanceOf(Function)); 5 | 6 | it("is a constructor", () => 7 | expect(new Singleton()).toBeInstanceOf(Singleton)); 8 | 9 | it("is a singleton", () => { 10 | expect(new Singleton()).toBe(new Singleton()); 11 | }); 12 | 13 | it("does not keep props in creator", () => { 14 | const singleton1 = new Singleton(); 15 | 16 | Object.keys(Singleton).forEach((extraKey) => { 17 | delete Singleton[extraKey]; 18 | }); 19 | const singleton2 = new Singleton(); 20 | expect(singleton1).toBe(singleton2); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /lessons/lesson03/code/singleton/src/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: sans-serif; 3 | } 4 | -------------------------------------------------------------------------------- /lessons/lesson03/code/smart-getter/.codesandbox/workspace.json: -------------------------------------------------------------------------------- 1 | { 2 | "responsive-preview": { 3 | "Mobile": [320, 675], 4 | "Tablet": [1024, 765], 5 | "Desktop": [1400, 800], 6 | "Desktop HD": [1920, 1080] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /lessons/lesson03/code/smart-getter/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Parcel Sandbox 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /lessons/lesson03/code/smart-getter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "smart-getter", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.html", 6 | "scripts": { 7 | "start": "parcel index.html --open", 8 | "build": "parcel build index.html" 9 | }, 10 | "dependencies": { 11 | "parcel-bundler": "^1.6.1" 12 | }, 13 | "devDependencies": { 14 | "@babel/core": "7.2.0" 15 | }, 16 | "resolutions": { 17 | "@babel/preset-env": "7.13.8" 18 | }, 19 | "keywords": [] 20 | } 21 | -------------------------------------------------------------------------------- /lessons/lesson03/code/smart-getter/src/get.js: -------------------------------------------------------------------------------- 1 | /** 2 | * get: Smart getter for object props 3 | * 4 | * Check tests for usage examples 5 | * 6 | * @param {any} obj 7 | * @param {string} property 8 | * @param {any} fallbackValue 9 | */ 10 | // export function get() {} 11 | -------------------------------------------------------------------------------- /lessons/lesson03/code/smart-getter/src/get.test.js: -------------------------------------------------------------------------------- 1 | import { get } from "./get"; 2 | 3 | const getObj = () => ({ 4 | a: 1, 5 | b: 2, 6 | c: { 7 | d: 3, 8 | e: 4, 9 | f: [ 10 | 0, 11 | 1, 12 | 2, 13 | 3, 14 | { 15 | a: 10, 16 | b: 20, 17 | c: { 18 | d: { 19 | e: 30, 20 | }, 21 | }, 22 | }, 23 | ], 24 | }, 25 | }); 26 | 27 | describe("get", () => { 28 | const invalidKeys = ["d", "c.z", "x.d", "c.f.100", "c.f.4.x", "z.f.4.b"]; 29 | let obj; 30 | beforeEach(() => (obj = getObj())); 31 | 32 | it("is a function", () => expect(get).toBeInstanceOf(Function)); 33 | 34 | it("returns valid properties", () => { 35 | expect(get(obj, "a")).toBe(1); 36 | expect(get(obj, "b")).toBe(2); 37 | expect(get(obj, "c.d")).toBe(3); 38 | expect(get(obj, "c.e")).toBe(4); 39 | expect(get(obj, "c.f.0")).toBe(0); 40 | expect(get(obj, "c.f.1")).toBe(1); 41 | expect(get(obj, "c.f.2")).toBe(2); 42 | expect(get(obj, "c.f.3")).toBe(3); 43 | expect(get(obj, "c.f.4.a")).toBe(10); 44 | expect(get(obj, "c.f.4.b")).toBe(20); 45 | expect(get(obj, "c.f.4.c.d.e")).toBe(30); 46 | }); 47 | 48 | it("returns undefined for invalid properties", () => { 49 | invalidKeys.forEach((invalidKey) => 50 | expect(get(obj, invalidKey)).toBe(undefined) 51 | ); 52 | }); 53 | 54 | it("returns fallback value if provided", () => { 55 | invalidKeys.forEach((invalidKey) => { 56 | const fallbackValue = Math.random(); 57 | expect(get(obj, invalidKey, fallbackValue)).toBe(fallbackValue); 58 | }); 59 | }); 60 | 61 | it("works not only with objects", () => { 62 | expect(get([1, 2, 3], "1")).toBe(2); 63 | expect(get("hello", "1")).toBe("e"); 64 | expect(get(23, "1")).toBe(undefined); 65 | expect(get(null, "1")).toBe(undefined); 66 | expect(get(undefined, "1")).toBe(undefined); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /lessons/lesson03/code/smart-getter/src/index.js: -------------------------------------------------------------------------------- 1 | // import { get } from "./get"; 2 | 3 | window.data = { 4 | person: { 5 | name: "Bob", 6 | details: { 7 | age: 18, 8 | catName: "Bars", 9 | }, 10 | }, 11 | }; 12 | 13 | document.getElementById("app").innerHTML = `

Getter

`; 14 | 15 | // window._get = get; 16 | -------------------------------------------------------------------------------- /lessons/lesson03/homework_lec1.md: -------------------------------------------------------------------------------- 1 | --- 2 | Зачем: Вы будете решать большое число достаточно простых задач. В аналогии с изучением иностранных языков - это упражнения на подстановку слов вместо пробелов. Нужно это для насмотренности на код и применения знаний элементарных конструкций языка. 3 | --- 4 | 5 | В ходе выполнения задания вы поработаете с двумя репозиториями, которые содержат в себе типичные задачи работы на Javascript. 6 | 7 | Работать вы будете с двумя репозиториями: 8 | 9 | https://github.com/mrdavidlaing/javascript-koans 10 | 11 | и 12 | 13 | https://github.com/liammclennan/JavaScript-Koans 14 | 15 | 16 | 17 | Вам нужно для каждого из репозиториев выполнить следующие шаги: 18 | 19 | - скачать (склонировать или просто скачать) к себе репозиторий 20 | - разобраться как запустить задачи (которые оформлены в виде тестов) 21 | - внести изменения так, чтобы решения задач проходили тесты 22 | - сделать коммит и пуллреквест с решением 23 | - сделать скриншот всех пройденных тестов 24 | 25 | 26 | 27 | Задание можно сдавать в формате: 28 | 29 | скриншоты пройденных тестов 30 | 31 | **или** 32 | 33 | скриншоты пройденных тестов и ссылка на пуллреквест с вашими изменениями (работу с гитом мы еще не разбирали, так что это задание со звездочкой) 34 | 35 | 36 | 37 | Критерии оценки: 38 | 39 | - решены все задачи в 1 репозитории (и предоставлены скриншоты пройденных тестов) - 5 баллов (по 5 за каждый) 40 | - сделан пуллреквест с изменениями по решенным задачам - 3 балла (по 3 за каждый репозиторий) 41 | 42 | **Принято ставится от 10 баллов** 43 | -------------------------------------------------------------------------------- /lessons/lesson03/homework_lec3.md: -------------------------------------------------------------------------------- 1 | --- 2 | Зачем: Продолжаем тренировку насмотренности и базовые операции. Большая часть задач не требует знаний алгоритмов, достаточно просто сформулировать последовательность шагов/действий. Вы тренируетесь писать функции, продолжаете знакомство с проверкой кода тестами. Продолжая аналогию с изучением иностранных языков - вы пишете прописи, тренируете руковой водить прямые линии и скругления. Нам здесь важен факт того, что вы прорешали нужное число задач (если бы могли дать вам задание решить одну задачу 20 способами - это задание было бы здесь, но увы). И т.к. это прописи - мы этот код не проверяем (нам важен факт написания кода, и это мы видим в вашем профиле), но при желании, вы можете прислать задачи на разбор. 3 | --- 4 | 5 | ### "Практика кодирования" 6 | 7 | 1. Зарегистрировать аккаунт на сайте [codewars.com](https://www.codewars.com/) (это можно сделать с помощью github аккаунта) - логин должен быть такой же как в вашем github профиле, имя и фамилия такие же как в личном кабинете ОТУС 8 | 2. Решить 19 задач из списка ниже 9 | 3. Убедиться, что в вашем профиле видно когда, сколько и какие задачи вы решили 10 | 4. Ссылку на ваш профиль codewars сбросить в чат по дз 11 | 12 | 13 | 14 | Задачи для решения: 15 | 16 | **String** 17 | 7 kyu https://www.codewars.com/kata/56d6b921c9ae3fd926000601 18 | 7 kyu https://www.codewars.com/kata/anagram-detection 19 | 7 kyu https://www.codewars.com/kata/disemvowel-trolls/ 20 | 7 kyu https://www.codewars.com/kata/mumbling/ 21 | 7 kyu https://www.codewars.com/kata/highest-and-lowest/ 22 | 7 kyu https://www.codewars.com/kata/isograms/ 23 | 7 kyu https://www.codewars.com/kata/char-code-calculation 24 | 7 kyu https://www.codewars.com/kata/cat-and-mouse-2d-version/ 25 | 26 | 27 | 28 | **Number** 29 | 8 kyu https://www.codewars.com/kata/opposite-number 30 | 8 kyu https://www.codewars.com/kata/even-or-odd 31 | 8 kyu https://www.codewars.com/kata/century-from-year 32 | 7 kyu https://www.codewars.com/kata/greatest-common-divisor 33 | 7 kyu https://www.codewars.com/kata/factorial 34 | 7 kyu https://www.codewars.com/kata/squares-sequence 35 | 7 kyu https://www.codewars.com/kata/concatenated-sum 36 | 7 kyu https://www.codewars.com/kata/filter-the-number 37 | 38 | 39 | 40 | **Array** 41 | 8 kyu https://www.codewars.com/kata/removing-elements 42 | 8 kyu https://www.codewars.com/kata/remove-duplicates-from-list 43 | 8 kyu https://www.codewars.com/kata/sum-of-positive 44 | 45 | Задание принято, если решено **минимум 19 задач** из списка с момента старта курса 46 | -------------------------------------------------------------------------------- /lessons/lesson04/code/DOMExamples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | DOM Examples 8 | 14 | 15 | 16 |

Пример работы с DOM

17 | 18 |
19 |
20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /lessons/lesson04/code/DOMExamples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "domexamples", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.html", 6 | "scripts": { 7 | "start": "parcel index.html --open", 8 | "build": "parcel build index.html" 9 | }, 10 | "dependencies": {}, 11 | "devDependencies": { 12 | "@babel/core": "7.2.0", 13 | "parcel-bundler": "^1.6.1" 14 | }, 15 | "keywords": [] 16 | } 17 | -------------------------------------------------------------------------------- /lessons/lesson04/code/DOMExamples/src/script.js: -------------------------------------------------------------------------------- 1 | const data = { 2 | name: "Bob", 3 | age: 18, 4 | }; 5 | 6 | function alertName() { 7 | alert(data.name); 8 | } 9 | window.alertName = alertName; // это нужно для `onclick="alertName()"` 10 | 11 | function alertAge() { 12 | alert(data.age); 13 | } 14 | 15 | function drawDataViaInnerHTML(el, data) { 16 | el.innerHTML = `
17 |

${data.name}

18 |
19 |

${data.age}

20 |
`; 21 | 22 | el.querySelector("h2").addEventListener("click", alertAge); 23 | } 24 | 25 | function drawDataViaCreateElement(el, data) { 26 | const div = document.createElement("div"); 27 | 28 | const h1 = document.createElement("h1"); 29 | h1.innerText = data.name; 30 | h1.addEventListener("click", alertName); 31 | 32 | const h2 = document.createElement("h2"); 33 | h2.innerText = data.age; 34 | h2.onclick = alertAge; 35 | 36 | const hr = document.createElement("hr"); 37 | 38 | div.appendChild(h1); 39 | div.appendChild(hr); 40 | div.appendChild(h2); 41 | el.appendChild(div); 42 | } 43 | 44 | drawDataViaInnerHTML(document.querySelector("#container1"), data); 45 | drawDataViaCreateElement(document.getElementById("container2"), data); 46 | -------------------------------------------------------------------------------- /lessons/lesson04/code/DOMExamplesPractice/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | DOM Examples 8 | 14 | 15 | 16 |

Пример работы с DOM

17 | 18 |
19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /lessons/lesson04/code/DOMExamplesPractice/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "domexamplespractice", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.html", 6 | "scripts": { 7 | "start": "parcel index.html --open", 8 | "build": "parcel build index.html" 9 | }, 10 | "dependencies": {}, 11 | "devDependencies": { 12 | "@babel/core": "7.2.0", 13 | "parcel-bundler": "^1.6.1" 14 | }, 15 | "keywords": [] 16 | } 17 | -------------------------------------------------------------------------------- /lessons/lesson04/code/DOMExamplesPractice/src/script.js: -------------------------------------------------------------------------------- 1 | const data = { 2 | coord: { 3 | lon: 37.62, 4 | lat: 55.75, 5 | }, 6 | weather: [ 7 | { 8 | id: 803, 9 | main: "Clouds", 10 | description: "broken clouds", 11 | icon: "04d", 12 | }, 13 | ], 14 | base: "stations", 15 | main: { 16 | temp: 2.15, 17 | feels_like: -3.03, 18 | temp_min: 2, 19 | temp_max: 2.22, 20 | pressure: 1029, 21 | humidity: 69, 22 | }, 23 | visibility: 10000, 24 | wind: { 25 | speed: 4, 26 | deg: 310, 27 | }, 28 | clouds: { 29 | all: 75, 30 | }, 31 | dt: 1605098072, 32 | sys: { 33 | type: 1, 34 | id: 9029, 35 | country: "RU", 36 | sunrise: 1605070630, 37 | sunset: 1605101407, 38 | }, 39 | timezone: 10800, 40 | id: 524901, 41 | name: "Moscow", 42 | cod: 200, 43 | }; 44 | 45 | /** 46 | * Функция должна отображать в элементе следующие данные 47 | * - имя города 48 | * - текущую температуру (main.temp) 49 | * - иконку для погоды (одну или все - weather[index]icon) 50 | * (см https://openweathermap.org/weather-conditions#How-to-get-icon-URL) 51 | * например http://openweathermap.org/img/wn/10d@2x.png 52 | * 53 | * Разметка любая 54 | */ 55 | function drawWeather(el, data) { 56 | // put your code here 57 | } 58 | 59 | drawWeather(document.querySelector("#container"), data); 60 | -------------------------------------------------------------------------------- /lessons/lesson04/code/dataStoragePractice/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Storage Examples 8 | 9 | 10 |

Пример работы с Storage

11 | 12 |

13 | Вы создаете прототип "списка задач". Пользователь может вводить данные в 14 | поле ввода, и видеть список введенных данных ниже. При обновлении страницы 15 | (или закрытии браузера) список сохраняется. 16 |

17 | 18 |
19 | 25 | 26 |
27 |
28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /lessons/lesson04/code/dataStoragePractice/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "datastoragepractice", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.html", 6 | "scripts": { 7 | "start": "parcel index.html --open", 8 | "build": "parcel build index.html" 9 | }, 10 | "dependencies": {}, 11 | "devDependencies": { 12 | "@babel/core": "7.2.0", 13 | "parcel-bundler": "^1.6.1" 14 | }, 15 | "keywords": [] 16 | } 17 | -------------------------------------------------------------------------------- /lessons/lesson04/code/dataStoragePractice/src/script.js: -------------------------------------------------------------------------------- 1 | (async function () { 2 | // Должна возвращать список пользователя 3 | // Если пользователь ничего не вводил - пустой список 4 | async function readList() { 5 | // put your code here 6 | } 7 | 8 | // Сохраняет список 9 | function saveList(items) { 10 | // put your code here 11 | } 12 | 13 | function drawList(el, items) { 14 | el.innerHTML = `
    ${items.map((el) => `
  1. ${el}
  2. `).join("")}
`; 15 | } 16 | 17 | // Получаем указатели на нужные элементы 18 | const form = document.querySelector("form"); 19 | const listEl = document.querySelector("#list"); 20 | 21 | // Читаем список при старте 22 | const items = await readList(); 23 | 24 | // и отрисовываем список 25 | drawList(listEl, items); 26 | 27 | form.addEventListener("submit", (ev) => { 28 | // чтобы не перезагружать страницу 29 | ev.preventDefault(); 30 | 31 | // читаем значение из формы 32 | const formElement = ev.target; 33 | const input = formElement.querySelector("input"); 34 | const value = input.value; 35 | input.value = ""; 36 | 37 | // добавляем элемент в список 38 | items.push(value); 39 | 40 | // обновляем список 41 | drawList(listEl, items); 42 | 43 | // сохраняем список 44 | saveList(items); 45 | }); 46 | })(); 47 | -------------------------------------------------------------------------------- /lessons/lesson04/code/fetchPractice/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Fetch Examples 8 | 9 | 10 |

Пример работы с fetch

11 | 12 |

13 | Вы создаете прототип "списка задач". Пользователь может вводить данные в 14 | поле ввода, и видеть список введенных данных ниже. При обновлении страницы 15 | (или закрытии браузера) список сохраняется. 16 |

17 | 18 |
19 | 25 | 26 |
27 |

28 |     
29 |     
30 |   
31 | 
32 | 


--------------------------------------------------------------------------------
/lessons/lesson04/code/fetchPractice/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "fetchpractice",
 3 |   "version": "1.0.0",
 4 |   "description": "",
 5 |   "main": "index.html",
 6 |   "scripts": {
 7 |     "start": "parcel index.html --open",
 8 |     "build": "parcel build index.html"
 9 |   },
10 |   "dependencies": {},
11 |   "devDependencies": {
12 |     "@babel/core": "7.2.0",
13 |     "parcel-bundler": "^1.6.1"
14 |   },
15 |   "keywords": []
16 | }
17 | 


--------------------------------------------------------------------------------
/lessons/lesson04/code/fetchPractice/src/script.js:
--------------------------------------------------------------------------------
 1 | (async function () {
 2 |   // Получаем указатели на нужные элементы
 3 |   const formEl = document.querySelector("form");
 4 |   const weatherInfoEl = document.querySelector("#weatherInfo");
 5 | 
 6 |   function showWeather(el, weatherInfo) {
 7 |     el.innerHTML = JSON.stringify(weatherInfo, null, 2);
 8 |   }
 9 | 
10 |   /**
11 |    * Функция должна делать запрос на
12 |    * https://api.openweathermap.org/data/2.5/weather?units=metric&q={{CITY_NAME}}&appid={{APP_ID}}
13 |    * где
14 |    *  {{CITY_NAME}} должен быть заменен на имя города
15 |    *  {{APP_ID}} должен быть заменен на ключ приложения
16 |    * Запрос возвращает данные в формате JSON
17 |    *
18 |    * функция должна возвращать (Promise) данные с информацией о погоде
19 |    * @param {string} cityName
20 |    */
21 |   async function getWeather(cityName) {
22 |     // put your code here
23 |   }
24 | 
25 |   formEl.addEventListener("submit", async (ev) => {
26 |     // чтобы не перезагружать страницу
27 |     ev.preventDefault();
28 | 
29 |     // читаем значение из формы
30 |     const formElement = ev.target;
31 |     const inputEl = formElement.querySelector("input");
32 |     const cityName = inputEl.value;
33 |     inputEl.value = "";
34 | 
35 |     const weather = await getWeather(cityName);
36 |     showWeather(weatherInfoEl, weather);
37 |   });
38 | })();
39 | 


--------------------------------------------------------------------------------
/lessons/lesson04/code/singleThreadInJs/index.html:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |   
 4 |     
 5 |     
 6 |     
 7 |     JS - однопоточный
 8 |   
 9 |   
10 |     

Пример проблемы однопоточности

11 | 12 |
13 |
14 | 15 |
16 |
17 | 18 | 19 |
20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /lessons/lesson04/code/singleThreadInJs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jssinglethread", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.html", 6 | "scripts": { 7 | "start": "parcel index.html --open", 8 | "build": "parcel build index.html" 9 | }, 10 | "dependencies": {}, 11 | "devDependencies": { 12 | "@babel/core": "7.2.0", 13 | "parcel-bundler": "^1.6.1" 14 | }, 15 | "keywords": [] 16 | } 17 | -------------------------------------------------------------------------------- /lessons/lesson04/code/singleThreadInJs/sandbox.config.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /lessons/lesson04/code/singleThreadInJs/src/script.js: -------------------------------------------------------------------------------- 1 | // Указатель на элемент для отображения времени 2 | const timeEl = document.querySelector("#time"); 3 | 4 | // Каждую секунду мы выполняем функцию 5 | setInterval(() => { 6 | // Которая в элементе отрисует текущее время 7 | timeEl.innerHTML = new Date().toLocaleTimeString(); 8 | }, 1000); 9 | 10 | // на кнопку 47 | // 48 | //
49 | // 50 | // `; 51 | 52 | // const incButton = document.querySelector('.inc') as HTMLButtonElement; 53 | // const decButton = document.querySelector('.dec') as HTMLButtonElement; 54 | // const plusButton = document.querySelector('.plus') as HTMLButtonElement; 55 | // const numberInput = document.querySelector('input[type="number"]') as HTMLInputElement; 56 | // const header = document.querySelector('h1') as HTMLHeadElement; 57 | 58 | // incButton.addEventListener('click', () => store.dispatch({ 59 | // type: 'inc' 60 | // })); 61 | 62 | // decButton.addEventListener('click', () => store.dispatch({ 63 | // type: 'dec' 64 | // })); 65 | 66 | // plusButton.addEventListener('click', () => store.dispatch({ 67 | // type:'plus', 68 | // payload: Number(numberInput.value) 69 | // })); 70 | 71 | // store.subscribe(() => header.innerHTML = `Counter: ${store.getState()?.counter}`); 72 | -------------------------------------------------------------------------------- /lessons/lesson34/code/reduxBasic/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "module": "commonjs", 5 | "jsx": "preserve", 6 | "esModuleInterop": true, 7 | "sourceMap": true, 8 | "allowJs": true, 9 | "lib": ["es6", "dom"], 10 | "rootDir": "src", 11 | "moduleResolution": "node" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lessons/lesson34/homework.md: -------------------------------------------------------------------------------- 1 | --- 2 | Зачем: Это подводящее задание, чтобы вы могли спроектировать приложение-чат, над которым вы будете работать позже. Ваша задача - разобраться с тем, для чего нужен Redux и как организовывать работу с данными (в т.ч. из сторонних систем) в приложении на базе Redux. 3 | --- 4 | 5 | **Задание:** 6 | 7 | Разработать структуру (структура, экшены, редьюсер) Redux store для приложения Чат. Структура должна позволять выполнять следующие операции: 8 | 9 | - получение списка сообщений 10 | - получение одного сообщения 11 | - отправка сообщения 12 | - получение списка пользователей 13 | - поиск по чату 14 | 15 | **Критерии приемки задания:** 16 | 17 | - структура позволяет выполнять все действия выше 18 | - предусмотрены экшены для асинхронных событий (получение сообщений, отправка сообщений) 19 | - получение данных (список сообщений, список пользователей, поиск сообщений) оформлены в виде селекторов(простых и параметризованных) 20 | - код оформлен в PR 21 | - проходит проверки линтером (и тестами) 22 | -------------------------------------------------------------------------------- /lessons/lesson34/images/EventBus.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson34/images/EventBus.jpeg -------------------------------------------------------------------------------- /lessons/lesson34/images/middlewares.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson34/images/middlewares.gif -------------------------------------------------------------------------------- /lessons/lesson34/images/redux-data-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson34/images/redux-data-flow.png -------------------------------------------------------------------------------- /lessons/lesson35/code/client.js: -------------------------------------------------------------------------------- 1 | const socket = new WebSocket("ws://localhost:3000"); 2 | 3 | const span = document.querySelector("span"); 4 | const button = document.querySelector("button"); 5 | const input = document.querySelector("input"); 6 | 7 | socket.onopen = function () { 8 | span.textContent = "ONLINE"; 9 | 10 | socket.onmessage = async function (msg) { 11 | console.log(msg.data); 12 | const data = await msg.data.text(); 13 | createMessageEl(data, ["message"]); 14 | }; 15 | }; 16 | 17 | socket.onclose = function () { 18 | span.textContent = "OFFLINE"; 19 | }; 20 | 21 | button.addEventListener("click", () => { 22 | if (socket.readyState === socket.OPEN) { 23 | socket.send(input.value); 24 | 25 | createMessageEl(input.value, ["message", "my-message"]); 26 | 27 | input.value = ""; 28 | } 29 | }); 30 | 31 | function createMessageEl(content, className) { 32 | const message = document.createElement("div"); 33 | 34 | message.classList.add(...className); 35 | 36 | message.textContent = content; 37 | document.body.append(message); 38 | } 39 | -------------------------------------------------------------------------------- /lessons/lesson35/code/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 | OFFLINE 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /lessons/lesson35/code/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "websockets", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev:server": "nodemon ./server.ts --watch . --ext ts,html" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "@types/ws": "^8.5.3", 14 | "nodemon": "^2.0.16", 15 | "ts-node": "^10.8.0", 16 | "typescript": "^4.7.2" 17 | }, 18 | "dependencies": { 19 | "ws": "^8.6.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lessons/lesson35/code/server.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { WebSocketServer } from "ws"; 3 | 4 | const wss = new WebSocketServer({ port: 3000 }); 5 | 6 | wss.on("connection", function connection(ws, req) { 7 | console.log(req.headers["sec-websocket-key"]); 8 | ws.id = req.headers["sec-websocket-key"]; 9 | console.log(wss.clients.size); 10 | ws.on("message", function message(data) { 11 | for (const socket of wss.clients) { 12 | if (socket.readyState === socket.OPEN && socket.id !== ws.id) { 13 | socket.send(data); 14 | } 15 | } 16 | 17 | console.log("received: %s", data); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /lessons/lesson35/code/style.css: -------------------------------------------------------------------------------- 1 | .message { 2 | background-color: violet; 3 | max-width: 500px; 4 | border-radius: 3px; 5 | margin-top: 10px; 6 | padding: 10px; 7 | } 8 | 9 | .message.my-message { 10 | background-color: cyan; 11 | } 12 | -------------------------------------------------------------------------------- /lessons/lesson35/code/types.d.ts: -------------------------------------------------------------------------------- 1 | import "ws"; 2 | 3 | declare module "ws" { 4 | export interface WebSocket { 5 | id?: string; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /lessons/lesson35/images/Archive_repo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson35/images/Archive_repo.png -------------------------------------------------------------------------------- /lessons/lesson35/images/REST_CRUD_operations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson35/images/REST_CRUD_operations.png -------------------------------------------------------------------------------- /lessons/lesson35/images/comparision1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson35/images/comparision1.png -------------------------------------------------------------------------------- /lessons/lesson35/images/comparision2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson35/images/comparision2.png -------------------------------------------------------------------------------- /lessons/lesson35/images/oauth_example1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson35/images/oauth_example1.png -------------------------------------------------------------------------------- /lessons/lesson35/images/oauth_example2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson35/images/oauth_example2.png -------------------------------------------------------------------------------- /lessons/lesson35/images/oauth_flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson35/images/oauth_flow.png -------------------------------------------------------------------------------- /lessons/lesson35/images/requestScope.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson35/images/requestScope.png -------------------------------------------------------------------------------- /lessons/lesson35/images/slack_RPC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson35/images/slack_RPC.png -------------------------------------------------------------------------------- /lessons/lesson35/images/slack_conversation_API.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson35/images/slack_conversation_API.png -------------------------------------------------------------------------------- /lessons/lesson35/images/ta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson35/images/ta.png -------------------------------------------------------------------------------- /lessons/lesson35/images/ta1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson35/images/ta1.png -------------------------------------------------------------------------------- /lessons/lesson35/images/tripAdvisor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson35/images/tripAdvisor.png -------------------------------------------------------------------------------- /lessons/lesson38/code/reduxDataLoading/.codesandbox/workspace.json: -------------------------------------------------------------------------------- 1 | { 2 | "responsive-preview": { 3 | "Mobile": [320, 675], 4 | "Tablet": [1024, 765], 5 | "Desktop": [1400, 800], 6 | "Desktop HD": [1920, 1080] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /lessons/lesson38/code/reduxDataLoading/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Parcel Sandbox 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /lessons/lesson38/code/reduxDataLoading/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reduxdataloading", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.html", 6 | "scripts": { 7 | "start": "parcel index.html --open", 8 | "build": "parcel build index.html" 9 | }, 10 | "dependencies": { 11 | "parcel-bundler": "^1.6.1", 12 | "redux": "4.0.5" 13 | }, 14 | "devDependencies": { 15 | "typescript": "4.2.3" 16 | }, 17 | "resolutions": { 18 | "@babel/preset-env": "7.13.8" 19 | }, 20 | "keywords": [] 21 | } 22 | -------------------------------------------------------------------------------- /lessons/lesson38/code/reduxDataLoading/src/actions.ts: -------------------------------------------------------------------------------- 1 | export const loading = () => ({ 2 | type: "LOADING", 3 | }); 4 | 5 | export const success = (payload: any) => ({ 6 | type: "SUCCESS", 7 | payload, 8 | }); 9 | 10 | export const error = (error: Error) => ({ 11 | type: "ERROR", 12 | error, 13 | }); 14 | -------------------------------------------------------------------------------- /lessons/lesson38/code/reduxDataLoading/src/api.ts: -------------------------------------------------------------------------------- 1 | const todos = [ 2 | { 3 | userId: 1, 4 | id: 1, 5 | title: "delectus aut autem", 6 | completed: false, 7 | }, 8 | { 9 | userId: 1, 10 | id: 2, 11 | title: "quis ut nam facilis et officia qui", 12 | completed: false, 13 | }, 14 | { 15 | userId: 1, 16 | id: 3, 17 | title: "fugiat veniam minus", 18 | completed: false, 19 | }, 20 | { 21 | userId: 1, 22 | id: 4, 23 | title: "et porro tempora", 24 | completed: true, 25 | }, 26 | { 27 | userId: 1, 28 | id: 5, 29 | title: "laboriosam mollitia et enim quasi adipisci quia provident illum", 30 | completed: false, 31 | }, 32 | { 33 | userId: 1, 34 | id: 6, 35 | title: "qui ullam ratione quibusdam voluptatem quia omnis", 36 | completed: false, 37 | }, 38 | { 39 | userId: 1, 40 | id: 7, 41 | title: "illo expedita consequatur quia in", 42 | completed: false, 43 | }, 44 | { 45 | userId: 1, 46 | id: 8, 47 | title: "quo adipisci enim quam ut ab", 48 | completed: true, 49 | }, 50 | { 51 | userId: 1, 52 | id: 9, 53 | title: "molestiae perspiciatis ipsa", 54 | completed: false, 55 | }, 56 | { 57 | userId: 1, 58 | id: 10, 59 | title: "illo est ratione doloremque quia maiores aut", 60 | completed: true, 61 | }, 62 | { 63 | userId: 1, 64 | id: 11, 65 | title: "vero rerum temporibus dolor", 66 | completed: true, 67 | }, 68 | { 69 | userId: 1, 70 | id: 12, 71 | title: "ipsa repellendus fugit nisi", 72 | completed: true, 73 | }, 74 | { 75 | userId: 1, 76 | id: 13, 77 | title: "et doloremque nulla", 78 | completed: false, 79 | }, 80 | ]; 81 | 82 | const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); 83 | 84 | export const getTodos = async () => { 85 | await delay(Math.random() * 1000 + 1500); 86 | if (Math.random() > 0.5) { 87 | return todos; 88 | } else { 89 | throw new Error("Something went wrong"); 90 | } 91 | }; 92 | -------------------------------------------------------------------------------- /lessons/lesson38/code/reduxDataLoading/src/index.ts: -------------------------------------------------------------------------------- 1 | import { State } from "./reducer"; 2 | import { store } from "./store"; 3 | import * as actions from "./actions"; 4 | import { getTodos } from "./api"; 5 | 6 | const el = document.querySelector("#app") as HTMLElement; 7 | 8 | type RenderData = { 9 | isLoading: boolean; 10 | data: any | undefined; 11 | error: Error | undefined; 12 | }; 13 | 14 | async function loadData() { 15 | // put your code here 16 | } 17 | 18 | const render = (props: RenderData) => { 19 | if (props.isLoading) { 20 | return (el.innerHTML = "Loading...."); 21 | } 22 | if (props.error) { 23 | return (el.innerHTML = `

${props.error.message}

`); 24 | } 25 | if (props.data) { 26 | return (el.innerHTML = `
${JSON.stringify(props.data, null, 2)}
`); 27 | } 28 | el.innerHTML = ``; 29 | el.querySelector("button")?.addEventListener("click", loadData); 30 | }; 31 | 32 | const selectData = (state: State): RenderData => ({ 33 | isLoading: state.isLoading, 34 | data: state.data, 35 | error: state.error, 36 | }); 37 | 38 | render(selectData(store.getState())); 39 | store.subscribe(() => render(selectData(store.getState()))); 40 | -------------------------------------------------------------------------------- /lessons/lesson38/code/reduxDataLoading/src/logger.ts: -------------------------------------------------------------------------------- 1 | import { Middleware } from "redux"; 2 | 3 | // export const loggerMiddleware: Middleware = ({ 4 | // dispatch, 5 | // getState 6 | // }) => next => action => { 7 | // // Залогируйте состояние до экшена 8 | // // Пустите экшен по цепочке дальше 9 | // // Залогируйте состояние после 10 | // // Верните экшен полученный из цепочки 11 | // }; 12 | -------------------------------------------------------------------------------- /lessons/lesson38/code/reduxDataLoading/src/reducer.ts: -------------------------------------------------------------------------------- 1 | import { Reducer } from "redux"; 2 | 3 | export type State = { 4 | isLoading: boolean; 5 | data: any | undefined; 6 | error: Error | undefined; 7 | }; 8 | 9 | const initialState: State = { 10 | isLoading: false, 11 | data: undefined, 12 | error: undefined, 13 | }; 14 | 15 | export const apiReducer: Reducer = (state = initialState, action) => { 16 | switch (action.type) { 17 | case "LOADING": 18 | return { ...state, isLoading: true }; 19 | case "SUCCESS": 20 | return { 21 | ...state, 22 | isLoading: false, 23 | data: action.payload, 24 | error: undefined, 25 | }; 26 | case "ERROR": 27 | return { 28 | ...state, 29 | isLoading: false, 30 | data: undefined, 31 | error: action.error, 32 | }; 33 | default: 34 | return state; 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /lessons/lesson38/code/reduxDataLoading/src/store.ts: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware } from "redux"; 2 | import { apiReducer } from "./reducer"; 3 | import { loggerMiddleware } from "./logger"; 4 | 5 | export const store = 6 | typeof loggerMiddleware === "function" 7 | ? createStore(apiReducer, applyMiddleware(loggerMiddleware)) 8 | : createStore(apiReducer); 9 | -------------------------------------------------------------------------------- /lessons/lesson38/code/reduxDataLoading/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "module": "commonjs", 5 | "jsx": "preserve", 6 | "esModuleInterop": true, 7 | "sourceMap": true, 8 | "allowJs": true, 9 | "lib": ["es6", "dom"], 10 | "rootDir": "src", 11 | "moduleResolution": "node" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lessons/lesson38/homework.md: -------------------------------------------------------------------------------- 1 | --- 2 | Зачем: Собираем все вместе. Берем верстку, структуру хля хранения состояния и дорабатываем это до полноценного приложения. Вы поработаете с дополнительным сервисом, который позволит вам в будущем организовывать взаимодействие нескольких пользователей. И на практике поймете, на сколько гибко вы выполнили подводящие задания 3 | --- 4 | 5 | ### "Разработать приложение "чат" на основе redux" 6 | 7 | Необходимо: 8 | 9 | - создать и настроить проект 10 | - реализовать приложение чат - которое позволяет отправлять сообщения и отображает входящие сообщения из канала в firebase (api и формат сообщения будет предоставлено): 11 | - использовать Redux структуру для приложения чат 12 | - создать UI для приложения 13 | - добавить функционал по отображению входящих сообщений 14 | - добавить функционал по отправке сообщений 15 | - добавить функционал по обработке смайликов (в виде картинок) 16 | - подготовить работу к сдаче 17 | - сделать ревью 2 других работ 18 | - сбросить ссылку на PR, опубликованный проект и рассмотренные пуллреквесты в чат с преподавателем 19 | 20 | #### Критерии оценки: 21 | 22 | - чат позволяет принимать сообщения - **2** балла 23 | - чат позволяет отправлять сообщения - **2** балла 24 | - чат поддерживает анимацию смайликов - **1** балл 25 | - сделано ревью 2 других проектов - **1** балл 26 | 27 | Задание не проверяется при не соответствии базовым требованиям к заданию! 28 | 29 | #### Статус принято от 5 баллов 30 | -------------------------------------------------------------------------------- /lessons/lesson38/images/middlewares.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson38/images/middlewares.gif -------------------------------------------------------------------------------- /lessons/lesson39/code/routing-examples/README.md: -------------------------------------------------------------------------------- 1 | # vanilla-client-side-routing-examples 2 | 3 | Created with CodeSandbox 4 | -------------------------------------------------------------------------------- /lessons/lesson39/code/routing-examples/examples/hash-api.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | const render = () => { 4 | const route = location.hash.replace("#", "") || "/"; 5 | document.getElementById("root").innerHTML = `

"${route}" page

`; 6 | }; 7 | 8 | // 1. Handle initial page load 9 | window.addEventListener("load", () => { 10 | render(); // 👈 11 | }); 12 | 13 | // 2. Handle hash changes 14 | window.addEventListener("hashchange", () => { 15 | render(); // 👈 16 | }); 17 | 18 | // 3. Catch tag clicks 19 | document.body.addEventListener("click", (event) => { 20 | if (!event.target.matches("a")) { 21 | return; 22 | } 23 | event.preventDefault(); 24 | const url = event.target.getAttribute("href"); 25 | location.hash = url; // doesn't reload page 26 | // location.href = url; // reloads page 27 | // location.replace(url); // reloads page 28 | }); 29 | -------------------------------------------------------------------------------- /lessons/lesson39/code/routing-examples/examples/history-api.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | const render = () => { 4 | const route = location.pathname; 5 | document.getElementById("root").innerHTML = `

"${route}" page

`; 6 | }; 7 | 8 | // 1. Handle initial page load 9 | window.addEventListener("load", () => { 10 | render(); // 👈 11 | }); 12 | 13 | // 2. Handle history navigations. alternative "window.onpopstate" 14 | window.addEventListener("popstate", (event) => { 15 | render(); 16 | }); 17 | 18 | // 3. Catch
tag clicks + trigger change handler 19 | document.body.addEventListener("click", (event) => { 20 | if (!event.target.matches("a")) { 21 | return; 22 | } 23 | event.preventDefault(); 24 | let url = event.target.getAttribute("href"); 25 | history.pushState({ foo: "bar" }, document.title, url); 26 | // history.replaceState({ foo: "bar" }, url, url); 27 | render(); // 👈 28 | }); 29 | -------------------------------------------------------------------------------- /lessons/lesson39/code/routing-examples/examples/practice.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | /** 4 | * TODO: modify router.js to support 5 | * 1. unsubscribe function. 6 | * Hint: inside Router.go function return unsubscribe function, 7 | * which will remove listener by id 8 | * 2. onLeave callback 9 | * Hint: Add 3rd 'onLeave' parameter to Router.on + save in listener object 10 | * Check in Router.handleListener if previousPath matches listener 11 | */ 12 | 13 | const render = (content) => 14 | (document.getElementById("root").innerHTML = `

${content}

`); 15 | 16 | const createLogger = 17 | (content, shouldRender = true) => 18 | (...args) => { 19 | console.log(`LOGGER: ${content} args=${JSON.stringify(args)}`); 20 | if (shouldRender) { 21 | render(content); 22 | } 23 | }; 24 | 25 | const router = Router(); 26 | 27 | const unsubscribe = router.on(/.*/, createLogger("/.*")); 28 | router.on( 29 | (path) => path === "/contacts", 30 | createLogger("/contacts"), // onEnter 31 | createLogger("[leaving] /contacts", false) // onLeave 32 | ); 33 | router.on("/about", createLogger("/about")); 34 | router.on("/about/us", createLogger("/about/us")); 35 | 36 | document.body.addEventListener("click", (event) => { 37 | if (!event.target.matches("a")) { 38 | return; 39 | } 40 | event.preventDefault(); 41 | let url = event.target.getAttribute("href"); 42 | router.go(url); 43 | unsubscribe(); 44 | }); 45 | -------------------------------------------------------------------------------- /lessons/lesson39/code/routing-examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Client-Side URL change examples 8 | 9 | 10 |
11 |

Client-side URL change examples

12 |
18 |
19 |
20 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /lessons/lesson39/code/routing-examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vanilla-client-side-routing-examples", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.html", 6 | "scripts": { 7 | "start": "serve", 8 | "build": "echo This is a static template, there is no bundler or bundling involved!" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/codesandbox-app/static-template.git" 13 | }, 14 | "keywords": [], 15 | "author": "Ives van Hoorne", 16 | "license": "MIT", 17 | "bugs": { 18 | "url": "https://github.com/codesandbox-app/static-template/issues" 19 | }, 20 | "homepage": "https://github.com/codesandbox-app/static-template#readme", 21 | "devDependencies": { 22 | "serve": "^11.2.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lessons/lesson39/code/routing-examples/sandbox.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "template": "static" 3 | } 4 | -------------------------------------------------------------------------------- /lessons/lesson39/hw.md: -------------------------------------------------------------------------------- 1 | --- 2 | Зачем: Во-первых вы разберетесь с тем, как устроен роутинг в современных приложениях (чтобы понимать, как работают готовые библиотеки, которыми вы будете пользоваться). Кроме этого вы потренируетесь оформлять свой код не в виде отдельного файла, а в виде полноценного npm-пакета, с которым смогут работать другие разработчики. 3 | --- 4 | 5 | ### "Разработка библиотеки клиентского роутинга" 6 | 7 | Необходимо: 8 | 9 | 1. создать и настроить проект 10 | 11 | 2. разработать библиотеку клиентского роутинга: 12 | 2.1 конфигурация роутов должна поддерживаться через функции/строки/регулярки 13 | 2.2 должна поддерживаться передача параметров в хуки роутера 14 | 2.3 реализовать поддержку асинхронных onBeforeEnter, onEnter, onLeave 15 | 2.4 добавить настройку для работы с hash/history api 16 | 2.5 опубликовать пакет 17 | 18 | 3. подготовить работу с сдаче: 19 | 3.1 сделать ревью **2** других работ 20 | 3.2 сбросить ссылку на PR, опубликованный проект и рассмотренные пуллреквесты в чат с преподавателем 21 | 22 | #### Критерии оценки: 23 | 24 | - роутер поддерживает роуты из строк - **1** балл 25 | - роутер поддерживает роуты из регулярных выражений - **1** балл 26 | - роутер поддерживает роуты из функций - **1** балл 27 | - роутер поддерживает асинхронные хуки - **1** балл 28 | - роутер поддерживает `onLeave` , `onEnter`, `onBeforeEnter` - **2** балла 29 | - роутер поддерживает переключение api - **1** балл 30 | - пакет опубликован - **1** балл 31 | - сделано ревью 2 проектов - **1** балл 32 | - роутер поддерживает проброс параметров в хуки - **2** балла 33 | 34 | #### Статус принято ставится от 8 баллов 35 | 36 | Задание не проверяется при не соответствии базовым требованиям к заданию! 37 | -------------------------------------------------------------------------------- /lessons/lesson40/code/.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build and Deploy 2 | 3 | on: [push, pull_request] 4 | 5 | permissions: 6 | contents: write 7 | 8 | jobs: 9 | build-and-deploy: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 🛎️ 13 | uses: actions/checkout@v3 14 | 15 | - name: Install and Build 🔧 # This example project is built using npm and outputs the result to the 'build' folder. Replace with the commands required to build your project, or remove this step entirely if your site is pre-built. 16 | run: | 17 | npm ci 18 | npm run build 19 | 20 | - name: Deploy 🚀 21 | uses: JamesIves/github-pages-deploy-action@v4.3.3 22 | with: 23 | branch: gh-pages # The branch the action should deploy to. 24 | folder: dist # The folder the action should deploy. 25 | -------------------------------------------------------------------------------- /lessons/lesson40/code/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .idea -------------------------------------------------------------------------------- /lessons/lesson40/code/.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /lessons/lesson40/code/.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /lessons/lesson40/code/README.md: -------------------------------------------------------------------------------- 1 | # webpack-gh-pages 2 | 3 | ###### Boilerplate for SPA deploy 4 | -------------------------------------------------------------------------------- /lessons/lesson40/code/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webpack-gh-pages", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "prepare": "husky install", 9 | "start": "webpack serve --node-env development", 10 | "buld:dev": "webapck --node-env development", 11 | "build": "webpack --node-env production" 12 | }, 13 | "keywords": [], 14 | "author": "", 15 | "license": "ISC", 16 | "devDependencies": { 17 | "@babel/core": "^7.14.3", 18 | "@babel/preset-typescript": "^7.13.0", 19 | "babel-loader": "^8.2.2", 20 | "html-webpack-plugin": "^5.3.1", 21 | "husky": "^6.0.0", 22 | "lint-staged": "^11.0.0", 23 | "prettier": "^2.3.1", 24 | "webpack": "^5.72.1", 25 | "webpack-cli": "^4.9.2", 26 | "webpack-dev-server": "^4.9.1" 27 | }, 28 | "lint-staged": { 29 | "*.{js,css,md,html,ts}": "prettier --write" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lessons/lesson40/code/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Single page app 8 | 13 | 14 | 15 | 20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /lessons/lesson40/code/src/index.ts: -------------------------------------------------------------------------------- 1 | function render(hash?: string) { 2 | const href = window.location.href; 3 | 4 | document.querySelector("#app")!.textContent = hash 5 | ? href.includes("#") 6 | ? href.replace(/#.*/, hash) 7 | : href + hash 8 | : window.location.href; 9 | } 10 | 11 | document.body.addEventListener("click", (ev) => { 12 | if ((ev.target as HTMLElement).matches("a")) { 13 | const hash = (ev.target as HTMLAnchorElement).hash; 14 | render(hash); 15 | } 16 | }); 17 | 18 | render(); 19 | -------------------------------------------------------------------------------- /lessons/lesson40/code/webpack.config.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require("path"); 2 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 3 | 4 | const NODE_ENV = process.env.NODE_ENV; 5 | 6 | module.exports = { 7 | entry: "./src/index.ts", 8 | output: { 9 | filename: "bundle.js", 10 | path: resolve(__dirname, "dist"), 11 | clean: true, 12 | }, 13 | mode: NODE_ENV, 14 | resolve: { 15 | extensions: [".js", ".ts"], 16 | }, 17 | module: { 18 | rules: [ 19 | { 20 | test: /\.ts$/, 21 | use: { 22 | loader: "babel-loader", 23 | options: { 24 | presets: ["@babel/preset-typescript"], 25 | }, 26 | }, 27 | }, 28 | ], 29 | }, 30 | plugins: [ 31 | new HtmlWebpackPlugin({ 32 | template: "public/index.html", 33 | }), 34 | ], 35 | devServer: { 36 | compress: true, 37 | port: 9000, 38 | watchFiles: ["public/index.html"], 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /lessons/lesson40/hw.md: -------------------------------------------------------------------------------- 1 | --- 2 | Зачем: Вы возьмете разработанных роутер, и примените его к существующему приложению. Заодно у вас будет шанс посмотреть по-новому на код, который вы писали ранее. Возможно (почти наверняка), вам придется его улучшить в процессе выполнения задачи. 3 | --- 4 | 5 | **Необходимо** 6 | 7 | - подключить разработанный роутер к приложению "Прогноз погоды" 8 | - использовать параметризованный роутинг для отображения погоды в конкретном городе 9 | - Добавить навигацию при запросе погоды (ввод нового города отображается на URL) 10 | - Обновить обработку навигации по истории (это должно быть сделано средствами роутинга) 11 | 12 | **Критерии приемки задачи** 13 | 14 | - экраны приложения рисуются роутером 15 | - показ погоды в городе обрабатывается параметризованным роутом, где имя города это параметр 16 | - ссылки из истории переведены на параметризованный роутинг (SPA ссылки, где логика загрузки и показа погоды обслуживается по навигации на роут, а не по клику) 17 | - приложение задеплоено на GithubPages, там же можно проверить работу ссылок 18 | - код оформлен в виде PR и проходит проверки (линтинг, тесты) 19 | - В приложении 3 экрана (главная, погода в городе, О проекте) 20 | -------------------------------------------------------------------------------- /lessons/lesson40/lecture.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Занятие 40 3 | description: Некоторые моменты про деплой одностраничных приложений 4 | --- 5 | 6 | # OTUS 7 | 8 | ## Javascript Basic 9 | 10 | 11 | 12 | Вопросы? 13 | 14 | 15 | 16 | ### Ссылки 17 | 18 | - [devServer.historyApiFallback](https://webpack.js.org/configuration/dev-server/#devserverhistoryapifallback) 19 | - [output.publicPath](https://webpack.js.org/configuration/output/#outputpublicpath) 20 | - [WebpackHTMLPlugin filename option](https://github.com/jantimon/html-webpack-plugin#options) 21 | - [404 page for Github Pages](https://docs.github.com/en/pages/getting-started-with-github-pages/creating-a-custom-404-page-for-your-github-pages-site) 22 | - [Пример настройки webpack для работы с history API](https://github.com/vvscode/webpack-gh-pages/pulls) 23 | - [Пример работы роутера с параметризацией](https://gzh7s.csb.app/#city=Moscow) 24 | 25 | 26 | 27 | ### [Домашнее задание](https://github.com/vvscode/otus--javascript-basic/blob/master/lessons/lesson40/homework.md) 28 | 29 | 30 | 31 | Вопросы? 32 | -------------------------------------------------------------------------------- /lessons/lesson42/hw.md: -------------------------------------------------------------------------------- 1 | --- 2 | Зачем: Дальнейшая переработка сайта, который вы сделали в начале курса. Теперь вы будете закреплять навыки работы с Redux (работа с сайд-эффектами, persistent storage). 3 | --- 4 | 5 | **Задание:** 6 | 7 | Использовать redux для сайта с прогнозом погоды. 8 | 9 | В репозитории с проектом "прогноз погоды" создать PR, который переводит работу с состоянием приложения на Redux. 10 | 11 | **Критерии выполнения задания:** 12 | 13 | - открыт PR 14 | - код проходит проверки линтером (и тесты) 15 | - все состояние приложения вынесено в Redux state 16 | - работа с API реализована посредством middleware 17 | - работа с хранимым состоянием (история) вынесена в middleware 18 | - код опубликован на GithubPages (и ссылка приложена к PR) 19 | - на странице работают Redux DevTools (можно открыть и посмотреть историю redux) 20 | -------------------------------------------------------------------------------- /lessons/lesson42/images/calendar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson42/images/calendar.png -------------------------------------------------------------------------------- /lessons/lesson42/images/folder-structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson42/images/folder-structure.png -------------------------------------------------------------------------------- /lessons/lesson43/hw.md: -------------------------------------------------------------------------------- 1 | --- 2 | Зачем: Знакомимся с современными инструментами разработки UI компонентов. С большой долей вероятности вы с ними столкнетесь в реальных проектах (если нет - это повод принести туда эти инструменты). 3 | --- 4 | 5 | **Необходимо:** 6 | 7 | - создать и настроить проект 8 | 9 | - настроить сторибук 10 | - настроить тесты с локи 11 | 12 | - создать компоненты: 13 | 14 | - заголовок (с поддержкой уровней) 15 | - параграф текста 16 | - пробельный блок (с горизонтальной линией) 17 | - схлопывающийся блок (может сворачиваться в заголовк или показывать контент) 18 | - картинка 19 | 20 | - опубликовать storybook на github pages 21 | 22 | - подготовить работу с сдаче\* 23 | - сделать ревью 2 других работ 24 | - сбросить ссылку на PR, опубликованный проект и рассмотренные пуллреквесты в част с преподавателем 25 | 26 | **Критерии приемки задания:** 27 | 28 | - задание оформлено в виде PR 29 | - код проходил проверки линтером (и тестами) 30 | - storybook задеплоен на GithubPages 31 | - реализованы все требуемые компоненты 32 | - все компоненты сопровождаются скриншотными тестами (настроена инфраструктура и скриншоты находятся в PR) 33 | -------------------------------------------------------------------------------- /lessons/lesson43/hw2.md: -------------------------------------------------------------------------------- 1 | --- 2 | Зачем: Закрепляем работу с компонентами 3 | --- 4 | 5 | **Необходимо:** 6 | 7 | - в прошлом проекте создать новые компоненты - за основу берем сайт "Прогоноз погоды" и переносим его разметку на компоненты React 8 | - продумать компоненты и их интерфейсы (какие свойства они принимают) 9 | - реализовать компоненты и story описывающие работу этих компонент 10 | 11 | Критерии приемки 12 | 13 | - реализованы все компоненты, необходимые для сборки "Прогноза погоды" (формы, списки, компонент-layout) 14 | - в storybook можно посмотреть все состояния компонент, проверить работу actions 15 | - storybook задеплоен на github pages (в отдельной директории, чтобы не мешать будущему проекту) 16 | -------------------------------------------------------------------------------- /lessons/lesson43/images/createElement.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson43/images/createElement.png -------------------------------------------------------------------------------- /lessons/lesson43/images/dom-reconciliation.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson43/images/dom-reconciliation.webp -------------------------------------------------------------------------------- /lessons/lesson43/images/vdom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson43/images/vdom.png -------------------------------------------------------------------------------- /lessons/lesson44/code/controlled-counter/.codesandbox/workspace.json: -------------------------------------------------------------------------------- 1 | { 2 | "responsive-preview": { 3 | "Mobile": [320, 675], 4 | "Tablet": [1024, 765], 5 | "Desktop": [1400, 800], 6 | "Desktop HD": [1920, 1080] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /lessons/lesson44/code/controlled-counter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "controlled-counter", 3 | "version": "1.0.0", 4 | "description": "", 5 | "keywords": [], 6 | "main": "src/index.tsx", 7 | "dependencies": { 8 | "@testing-library/jest-dom": "5.14.1", 9 | "@testing-library/react": "11.2.7", 10 | "@testing-library/user-event": "13.1.9", 11 | "react": "17.0.2", 12 | "react-dom": "17.0.2", 13 | "react-scripts": "4.0.0" 14 | }, 15 | "devDependencies": { 16 | "@types/react": "17.0.0", 17 | "@types/react-dom": "17.0.0", 18 | "typescript": "4.1.3" 19 | }, 20 | "scripts": { 21 | "start": "react-scripts start", 22 | "build": "react-scripts build", 23 | "test": "react-scripts test --env=jsdom", 24 | "eject": "react-scripts eject" 25 | }, 26 | "browserslist": [ 27 | ">0.2%", 28 | "not dead", 29 | "not ie <= 11", 30 | "not op_mini all" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /lessons/lesson44/code/controlled-counter/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 16 | 25 | React App 26 | 27 | 28 | 29 | 30 |
31 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /lessons/lesson44/code/controlled-counter/src/Counter.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { CounterButton } from "./CounterButton"; 4 | 5 | export class Counter extends React.Component<{}, { value: number }> { 6 | state = { 7 | value: 5, 8 | }; 9 | 10 | inc = () => 11 | this.setState((state) => ({ 12 | value: state.value + 1, 13 | })); 14 | 15 | dec = () => 16 | this.setState((state) => ({ 17 | value: state.value - 1, 18 | })); 19 | 20 | render() { 21 | return ( 22 | 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lessons/lesson44/code/controlled-counter/src/CounterButton.tsx: -------------------------------------------------------------------------------- 1 | export function CounterButton({ 2 | value, 3 | inc, 4 | dec, 5 | }: { 6 | value: number; 7 | inc: () => void; 8 | dec: () => void; 9 | }) { 10 | return ( 11 | <> 12 | 13 | {value} 14 | 15 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /lessons/lesson44/code/controlled-counter/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { render } from "react-dom"; 2 | 3 | import { Counter } from "./Counter"; 4 | 5 | const rootElement = document.getElementById("root"); 6 | render(, rootElement); 7 | -------------------------------------------------------------------------------- /lessons/lesson44/code/controlled-counter/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["./src/**/*"], 3 | "compilerOptions": { 4 | "strict": true, 5 | "esModuleInterop": true, 6 | "lib": ["dom", "es2015"], 7 | "jsx": "react-jsx" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lessons/lesson44/code/counter-button/.codesandbox/workspace.json: -------------------------------------------------------------------------------- 1 | { 2 | "responsive-preview": { 3 | "Mobile": [320, 675], 4 | "Tablet": [1024, 765], 5 | "Desktop": [1400, 800], 6 | "Desktop HD": [1920, 1080] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /lessons/lesson44/code/counter-button/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "counter-button", 3 | "version": "1.0.0", 4 | "description": "", 5 | "keywords": [], 6 | "main": "src/index.tsx", 7 | "dependencies": { 8 | "@testing-library/jest-dom": "5.14.1", 9 | "@testing-library/react": "11.2.7", 10 | "@testing-library/user-event": "13.1.9", 11 | "react": "17.0.2", 12 | "react-dom": "17.0.2", 13 | "react-scripts": "4.0.0" 14 | }, 15 | "devDependencies": { 16 | "@types/react": "17.0.0", 17 | "@types/react-dom": "17.0.0", 18 | "typescript": "4.1.3" 19 | }, 20 | "scripts": { 21 | "start": "react-scripts start", 22 | "build": "react-scripts build", 23 | "test": "react-scripts test --env=jsdom", 24 | "eject": "react-scripts eject" 25 | }, 26 | "browserslist": [ 27 | ">0.2%", 28 | "not dead", 29 | "not ie <= 11", 30 | "not op_mini all" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /lessons/lesson44/code/counter-button/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 16 | 25 | React App 26 | 27 | 28 | 29 | 30 |
31 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /lessons/lesson44/code/counter-button/src/CounterButton.test.tsx: -------------------------------------------------------------------------------- 1 | import { render, screen } from "@testing-library/react"; 2 | import userEvent from "@testing-library/user-event"; 3 | import "@testing-library/jest-dom"; 4 | 5 | import { CounterButton } from "./CounterButton"; 6 | 7 | describe("CounterButton", () => { 8 | it("is a Component", () => { 9 | expect(CounterButton).toBeInstanceOf(Function); 10 | }); 11 | 12 | describe("functionality", () => { 13 | beforeEach(() => { 14 | render(); 15 | }); 16 | 17 | it("renders button with 0", () => { 18 | expect(screen.getByTestId("counter-button")).toBeInTheDocument(); 19 | expect(screen.getByTestId("counter-button").innerHTML).toBe("0"); 20 | }); 21 | 22 | it("increments counter on every click", () => { 23 | expect(screen.getByTestId("counter-button").innerHTML).toBe("0"); 24 | 25 | userEvent.click(screen.getByTestId("counter-button")); 26 | expect(screen.getByTestId("counter-button").innerHTML).toBe("1"); 27 | 28 | userEvent.click(screen.getByTestId("counter-button")); 29 | expect(screen.getByTestId("counter-button").innerHTML).toBe("2"); 30 | 31 | userEvent.click(screen.getByTestId("counter-button")); 32 | expect(screen.getByTestId("counter-button").innerHTML).toBe("3"); 33 | }); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /lessons/lesson44/code/counter-button/src/CounterButton.tsx: -------------------------------------------------------------------------------- 1 | // put your code here 2 | -------------------------------------------------------------------------------- /lessons/lesson44/code/counter-button/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { render } from "react-dom"; 2 | 3 | import { CounterButton } from "./CounterButton"; 4 | 5 | const rootElement = document.getElementById("root"); 6 | render(, rootElement); 7 | -------------------------------------------------------------------------------- /lessons/lesson44/code/counter-button/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["./src/**/*"], 3 | "compilerOptions": { 4 | "strict": true, 5 | "esModuleInterop": true, 6 | "lib": ["dom", "es2015"], 7 | "jsx": "react-jsx" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lessons/lesson44/images/unidirectional-data-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson44/images/unidirectional-data-flow.png -------------------------------------------------------------------------------- /lessons/lesson45/images/tree-state.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson45/images/tree-state.png -------------------------------------------------------------------------------- /lessons/lesson45/images/tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson45/images/tree.png -------------------------------------------------------------------------------- /lessons/lesson46/code/reactredux/.codesandbox/workspace.json: -------------------------------------------------------------------------------- 1 | { 2 | "responsive-preview": { 3 | "Mobile": [320, 675], 4 | "Tablet": [1024, 765], 5 | "Desktop": [1400, 800], 6 | "Desktop HD": [1920, 1080] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /lessons/lesson46/code/reactredux/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reactredux", 3 | "version": "1.0.0", 4 | "description": "", 5 | "keywords": [], 6 | "main": "src/index.tsx", 7 | "dependencies": { 8 | "@reduxjs/toolkit": "1.6.0", 9 | "react": "17.0.2", 10 | "react-dom": "17.0.2", 11 | "react-redux": "7.2.4", 12 | "react-scripts": "4.0.0" 13 | }, 14 | "devDependencies": { 15 | "@types/react": "17.0.0", 16 | "@types/react-dom": "17.0.0", 17 | "typescript": "4.1.3" 18 | }, 19 | "scripts": { 20 | "start": "react-scripts start", 21 | "build": "react-scripts build", 22 | "test": "react-scripts test --env=jsdom", 23 | "eject": "react-scripts eject" 24 | }, 25 | "browserslist": [ 26 | ">0.2%", 27 | "not dead", 28 | "not ie <= 11", 29 | "not op_mini all" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /lessons/lesson46/code/reactredux/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 16 | 25 | React App 26 | 27 | 28 | 29 | 30 |
31 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /lessons/lesson46/code/reactredux/src/1_pureReact/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { store } from "../store"; 3 | import { counterSlice } from "../store/counter"; 4 | 5 | export class App extends React.Component { 6 | private unsubscribe = () => {}; 7 | 8 | componentDidMount() { 9 | this.unsubscribe = store.subscribe(() => this.forceUpdate()); 10 | } 11 | 12 | componentWillUnmount() { 13 | this.unsubscribe(); 14 | } 15 | 16 | getComponentData() { 17 | const state = store.getState(); 18 | return { 19 | count: state.counter, 20 | totalCount: state.actionsCounter.count, 21 | }; 22 | } 23 | 24 | render() { 25 | const componentData = this.getComponentData(); 26 | return ( 27 | <> 28 |

Total clicks: {componentData.totalCount}

29 | 36 | 41 | {componentData.count} 42 | 47 | 54 | 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lessons/lesson46/code/reactredux/src/2_withHOC/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { store } from "../store"; 3 | import { counterSlice } from "../store/counter"; 4 | import { withRedux } from "./withRedux"; 5 | 6 | const getAppProps = (state) => { 7 | return { 8 | count: state.counter, 9 | totalCount: state.actionsCounter.count, 10 | }; 11 | }; 12 | 13 | class RawApp extends React.Component< 14 | ReturnType & { 15 | dispatch: (action: any) => void; 16 | } 17 | > { 18 | render() { 19 | return ( 20 | <> 21 |

Total clicks: {this.props.totalCount}

22 | 29 | 34 | {this.props.count} 35 | 40 | 47 | 48 | ); 49 | } 50 | } 51 | 52 | export const App = withRedux>( 53 | RawApp, 54 | getAppProps 55 | ); 56 | -------------------------------------------------------------------------------- /lessons/lesson46/code/reactredux/src/2_withHOC/withRedux.tsx: -------------------------------------------------------------------------------- 1 | import React, { Dispatch } from "react"; 2 | import { AnyAction } from "redux"; 3 | import { store } from "../store"; 4 | 5 | interface DispatchProp { 6 | dispatch?: Dispatch; 7 | } 8 | 9 | type State = Record; 10 | 11 | // if you want to get more info 12 | // try to check https://gist.github.com/gaearon/1d19088790e70ac32ea636c025ba424e 13 | export function withRedux( 14 | TargetComponent: React.ComponentType, 15 | getPropsFromRedux: (state: State) => ComponentProps 16 | ): React.ComponentType> { 17 | class WrappedComponent extends React.Component< 18 | Omit>, 19 | State 20 | > { 21 | storeSubscription = () => {}; 22 | 23 | render() { 24 | return ( 25 | 30 | ); 31 | } 32 | 33 | componentDidMount() { 34 | this.storeSubscription = store.subscribe(() => this.forceUpdate()); 35 | } 36 | 37 | componentWillUnmount() { 38 | this.storeSubscription(); 39 | } 40 | } 41 | 42 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 43 | ( 44 | WrappedComponent as any 45 | ).displayName = `${TargetComponent.displayName}ConnectedToRedux`; 46 | 47 | return WrappedComponent; 48 | } 49 | -------------------------------------------------------------------------------- /lessons/lesson46/code/reactredux/src/3_react-redux/Counter.tsx: -------------------------------------------------------------------------------- 1 | import { AnyAction, bindActionCreators, Dispatch } from "@reduxjs/toolkit"; 2 | import React from "react"; 3 | import { connect } from "react-redux"; 4 | import { store } from "../store"; 5 | import { counterSlice } from "../store/counter"; 6 | 7 | const mapStateToProps = (state: ReturnType) => ({ 8 | count: state.counter, 9 | totalCount: state.actionsCounter.count, 10 | }); 11 | 12 | // https://react-redux.js.org/api/connect#mapdispatchtoprops-object--dispatch-ownprops--object 13 | const mapDispatchToProps = (dispatch: Dispatch) => { 14 | return bindActionCreators( 15 | { 16 | incrementByAmount: counterSlice.actions.incrementByAmount, 17 | decrement: counterSlice.actions.decrement, 18 | increment: counterSlice.actions.increment, 19 | }, 20 | dispatch 21 | ); 22 | }; 23 | // const mapDispatchToProps = { 24 | // incrementByAmount: counterSlice.actions.incrementByAmount, 25 | // decrement: counterSlice.actions.decrement, 26 | // increment: counterSlice.actions.increment 27 | // }; 28 | 29 | type ConnectedProps< 30 | MapData extends (...args: any) => any, 31 | MapActions extends (...args: any) => any | Record 32 | > = ReturnType & 33 | (MapActions extends Function ? ReturnType : MapActions); 34 | 35 | class RawCounter extends React.Component< 36 | ConnectedProps 37 | > { 38 | render() { 39 | return ( 40 | <> 41 |

Total clicks: {this.props.totalCount}

42 | 43 | 44 | {this.props.count} 45 | 46 | 47 | 48 | ); 49 | } 50 | } 51 | 52 | export const Counter = connect(mapStateToProps, mapDispatchToProps)(RawCounter); 53 | -------------------------------------------------------------------------------- /lessons/lesson46/code/reactredux/src/3_react-redux/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { store } from "../store"; 3 | import { Provider } from "react-redux"; 4 | import { Counter } from "./Counter"; 5 | 6 | export const App = () => ( 7 | 8 | 9 | 10 | ); 11 | -------------------------------------------------------------------------------- /lessons/lesson46/code/reactredux/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { render } from "react-dom"; 2 | 3 | let App = () =>

React-Redux

; 4 | // import { App } from "./1_pureReact"; 5 | // import { App } from "./2_withHOC"; 6 | // import { App } from "./3_react-redux"; 7 | 8 | const rootElement = document.getElementById("root"); 9 | render(, rootElement); 10 | -------------------------------------------------------------------------------- /lessons/lesson46/code/reactredux/src/store/actionsCounter.ts: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | export const actionsCounterSlice = createSlice({ 4 | name: "actionsCounter", 5 | initialState: { 6 | count: 0, 7 | }, 8 | reducers: {}, 9 | extraReducers: (builder) => { 10 | builder.addDefaultCase((state) => { 11 | state.count++; 12 | }); 13 | }, 14 | }); 15 | -------------------------------------------------------------------------------- /lessons/lesson46/code/reactredux/src/store/counter.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, PayloadAction } from "@reduxjs/toolkit"; 2 | 3 | export const counterSlice = createSlice({ 4 | name: "counter", 5 | initialState: 0, 6 | reducers: { 7 | increment: (state) => state + 1, 8 | decrement: (state) => state - 1, 9 | incrementByAmount: (state, action: PayloadAction) => 10 | state + action.payload, 11 | }, 12 | }); 13 | -------------------------------------------------------------------------------- /lessons/lesson46/code/reactredux/src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { configureStore } from "@reduxjs/toolkit"; 2 | import { actionsCounterSlice } from "./actionsCounter"; 3 | import { counterSlice } from "./counter"; 4 | 5 | export const store = configureStore({ 6 | reducer: { 7 | counter: counterSlice.reducer, 8 | actionsCounter: actionsCounterSlice.reducer, 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /lessons/lesson46/code/reactredux/src/styles.css: -------------------------------------------------------------------------------- 1 | .App { 2 | font-family: sans-serif; 3 | text-align: center; 4 | } 5 | -------------------------------------------------------------------------------- /lessons/lesson46/code/reactredux/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["./src/**/*"], 3 | "compilerOptions": { 4 | "strict": true, 5 | "esModuleInterop": true, 6 | "lib": ["dom", "es2015"], 7 | "jsx": "react-jsx" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lessons/lesson48/homework.md: -------------------------------------------------------------------------------- 1 | --- 2 | Зачем: Тренируемся писать консольные приложения и публиковать их в Github (вы уже делали это с API, но инструменты командной строки немного отличаются). Учимся работать с файловой системой и запуском команд из nodejs. 3 | --- 4 | 5 | ### "Написать консольное приложение для публикации страниц на github" 6 | 7 | #### Необходимо: 8 | 9 | - создать и настроить проект\* 10 | 11 | - реализовать скрипт командной строки, который поддерживает ввод параметров 12 | 13 | - через аргументы 14 | - через интерфейс с запросами в консоли 15 | 16 | - позволяет опубликовать проект (собрать его при необходимости) на github pages с заданными параметрами 17 | - команда для сборки (если нужно) 18 | - директория для публикации 19 | - репозиторий для публикации 20 | - настройки доступа к репозиторию 21 | 22 | - опубликовать, чтобы можно было использовать через npx 23 | - оформить README с подробным описанием и gif с демонстрацией работы 24 | 25 | - подготовить работу с сдаче\* 26 | - сделать ревью 2 других работ 27 | - сбросить ссылку на PR, опубликованный проект и рассмотренные пуллреквесты в част с преподавателем 28 | 29 | #### Критерии оценки 30 | 31 | - реализованная команда публикует проект - **2** балла 32 | - поддерживается сборка проекта перед публикацией - **1** балл 33 | - поддерживается настройка репозитория - **1** балл 34 | - поддерживается настройка авторизации - **1** балл 35 | - поддерживается ввод через параметры - **1** балл 36 | - поддерживается ввод через запросы - **1** балл 37 | - проверены 2 задания - **1** балл 38 | - пакет опубликован и доступен через npx - **1** балл 39 | 40 | #### Задание не проверяется при не соответствии базовым требованиям к заданию 41 | 42 | Принято от **6** баллов 43 | -------------------------------------------------------------------------------- /lessons/lesson49/code/assets/read.txt: -------------------------------------------------------------------------------- 1 | Hey, I'm file and you can read me! -------------------------------------------------------------------------------- /lessons/lesson49/code/assets/song.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson49/code/assets/song.mp3 -------------------------------------------------------------------------------- /lessons/lesson49/code/assets/tmp.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "username": "Bob Ross", 4 | "paintings": 359 5 | } 6 | -------------------------------------------------------------------------------- /lessons/lesson49/code/assets/tmp.txt: -------------------------------------------------------------------------------- 1 | 1 2 3 4 5 6 7 8 9 10 -------------------------------------------------------------------------------- /lessons/lesson49/code/commander/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "commander", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "commander": "^8.1.0" 14 | }, 15 | "devDependencies": { 16 | "@types/node": "^16.4.7", 17 | "ts-node": "^10.1.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lessons/lesson49/code/commander/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noEmit": true, 4 | "module": "CommonJS", 5 | "target": "ES2020", 6 | "esModuleInterop": true, 7 | "resolveJsonModule": true 8 | }, 9 | "include": ["*.ts"], 10 | "exclude": ["node_modules"] 11 | } 12 | -------------------------------------------------------------------------------- /lessons/lesson49/code/examples/1-writeFileSync.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | 3 | fs.writeFileSync("../assets/writeFile.txt", "hello! I'm new born file!", { 4 | encoding: "utf-8", 5 | }); 6 | -------------------------------------------------------------------------------- /lessons/lesson49/code/examples/2-readFileSync.ts: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | 3 | const data = fs.readFileSync("./2-readFileSync.ts", "utf8"); 4 | console.log(`Data length: ${data.length}`); 5 | console.log(data); 6 | -------------------------------------------------------------------------------- /lessons/lesson49/code/examples/3-readWriteFileCb.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | 3 | fs.readFile("../assets/song.mp3", (err, data) => { 4 | if (err) console.log(err); 5 | 6 | console.log(data); 7 | console.log(data.length); 8 | 9 | fs.writeFile("../assets/mySong.mp3", data, (err) => { 10 | if (err) console.log(err); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /lessons/lesson49/code/examples/4-fsWatch.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | 3 | const watcher = fs.watch( 4 | "./assets/tmp.txt", 5 | { encoding: "utf-8" }, 6 | (eventType, filename) => { 7 | // https://stackoverflow.com/questions/12978924/fs-watch-fired-twice-when-i-change-the-watched-file 8 | console.log(eventType); 9 | 10 | if (filename) { 11 | console.log(filename, "\n"); 12 | } 13 | } 14 | ); 15 | 16 | watcher.on("change", (event, filename) => { 17 | console.log(event, filename, "\n"); 18 | }); 19 | 20 | watcher.on("close", () => { 21 | console.log("closed!"); 22 | }); 23 | 24 | setTimeout(() => watcher.close(), 10000); 25 | -------------------------------------------------------------------------------- /lessons/lesson49/code/examples/5-fsStat.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | 3 | (async function () { 4 | const stats = []; 5 | 6 | const filenames = ["../assets/tmp.json", "../assets/fileThatNotExists.txt"]; 7 | 8 | filenames.forEach((file) => { 9 | stats.push(fs.promises.stat(file, { bigint: true })); 10 | }); 11 | 12 | for (let i = 0; i < stats.length; i++) { 13 | try { 14 | await stats[i]; 15 | } catch (e) { 16 | console.log("error!"); 17 | } 18 | } 19 | 20 | console.log(stats, "stats"); 21 | })(); 22 | -------------------------------------------------------------------------------- /lessons/lesson49/code/examples/6-dirRead.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | 3 | async function print(path: string) { 4 | const dir = await fs.promises.opendir(path); 5 | 6 | console.log(dir); 7 | 8 | for await (const dirent of dir) { 9 | console.log(dirent.name); 10 | console.log(dirent.name, dirent.isFile()); 11 | // console.log(dirent.isDirectory()); 12 | } 13 | } 14 | 15 | print("./").catch(console.error); 16 | 17 | function printCb(path) { 18 | fs.opendir(path, (err, dir) => { 19 | if (err) console.log(err); 20 | 21 | console.log(dir); 22 | }); 23 | } 24 | 25 | // printCb('./'); 26 | -------------------------------------------------------------------------------- /lessons/lesson49/code/examples/7-openAndReadFile.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import { Buffer } from "buffer"; 3 | 4 | fs.open( 5 | new URL( 6 | "file:///D:/JS/otus--javascript-basic/lessons/lesson49/code/assets/tmp.txt" 7 | ), 8 | "w", 9 | (err, fd) => { 10 | if (err) console.log(err); 11 | 12 | const buffer = Buffer.alloc(20); 13 | const offset = 0; 14 | const length = 20; 15 | const position = null; 16 | 17 | console.log(buffer, "before read"); 18 | 19 | fs.read(fd, buffer, offset, length, position, (err, bytesRead, buffer) => { 20 | if (err) console.log(err); 21 | 22 | console.log(bytesRead, "number of bytes read"); 23 | console.log(buffer, "after read"); 24 | console.log(buffer.toString("hex")); 25 | }); 26 | } 27 | ); 28 | -------------------------------------------------------------------------------- /lessons/lesson49/code/examples/8-fsWriteStream.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import { Buffer } from "buffer"; 3 | 4 | // if (fs.existsSync("./files/write.txt")) { 5 | // fs.unlink("./files/write.txt", () => console.log("file deleted!")); 6 | // } 7 | 8 | const stream = fs.createWriteStream("../assets/write.txt"); 9 | 10 | stream.on("ready", () => { 11 | stream.write("Hello I'm newborn file!:)", (err) => console.log(err)); 12 | 13 | const buffer = Buffer.alloc(40); 14 | buffer.write("\nI'm writing buffer right now!"); 15 | 16 | console.log(buffer); 17 | 18 | stream.write(buffer, (err) => { 19 | if (err) console.log(err); 20 | stream.close(); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /lessons/lesson49/code/examples/9-fsReadStream.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | 3 | // const stream = fs.createReadStream("../assets/read.txt"); 4 | const stream = fs.createReadStream("../assets/read.txt", { 5 | highWaterMark: 1000, 6 | }); 7 | 8 | // stream.on("readable", () => { 9 | // console.log("readable"); 10 | // // const data = stream.read(); 11 | // const data = stream.read(1); 12 | 13 | // if (data) console.log(data.toString()); 14 | // }); 15 | 16 | // stream.on('data', (chunk: Buffer) => { 17 | // console.log('data'); 18 | // console.log(chunk.toString()) 19 | // }) 20 | 21 | // stream.on('end', () => console.log('done')); 22 | 23 | (async function () { 24 | const stream = fs.createReadStream("../assets/read.txt"); 25 | 26 | for await (const chunk of stream) { 27 | console.log(chunk.toString()); 28 | } 29 | })(); 30 | -------------------------------------------------------------------------------- /lessons/lesson49/code/examples/os.ts: -------------------------------------------------------------------------------- 1 | import os from "os"; 2 | 3 | //Example-1 4 | // if (os.EOL === "\n") { 5 | // console.log(String.raw`You are working with line \n delimeter. Posix`); 6 | // } else if (os.EOL === `\r\n`) { 7 | // console.log(String.raw`You are working with line \r\n delimeter. Windows`); 8 | // } 9 | 10 | // Example-2 11 | //https://nodejs.org/dist/latest-v16.x/docs/api/os.html#os_os_constants_1 12 | // console.log(os.constants); 13 | 14 | // Example-3 15 | // 'arm', 'arm64', 'ia32', 'mips', ..., 'x32', 'x64' 16 | // console.log(os.arch()); 17 | 18 | // Example-4 19 | // console.log(os.cpus()); 20 | 21 | // Example-5 22 | // console.log(os.endianness()); 23 | 24 | // Examples-6 25 | // console.table({ 26 | // 'Memory in bytes': os.freemem(), 27 | // 'Mermoy in Kb': Math.round(os.freemem() / 1024), 28 | // 'Mermoy in Mb': Math.round(os.freemem() / 1024 / 1024), 29 | // 'Mermoy in Gb': Math.round(os.freemem() / 1024 / 1024 / 1024), 30 | // }); 31 | 32 | // console.table({ 33 | // 'Memory in bytes': os.totalmem(), 34 | // 'Mermoy in Kb': Math.round(os.totalmem() / 1024), 35 | // 'Mermoy in Mb': Math.round(os.totalmem() / 1024 / 1024), 36 | // 'Mermoy in Gb': Math.round(os.totalmem() / 1024 / 1024 / 1024), 37 | // }); 38 | 39 | // Example-7 40 | // console.log(os.homedir()); 41 | 42 | // Exammple-8 43 | // console.log(os.hostname()); 44 | 45 | // Example-9 46 | // Unix-specific 47 | // console.log(os.loadavg()); 48 | 49 | // Example-10 50 | // console.log(os.networkInterfaces()); 51 | 52 | // Example-11 53 | // console.log(os.platform()); 54 | 55 | // Example-12 56 | // console.log(os.release()); 57 | 58 | // Example-13 59 | // console.log(os.tmpdir()) 60 | 61 | // Example-14 62 | // console.log(os.type()); 63 | 64 | // Example-15 65 | // console.table({ 66 | // 'seconds': os.uptime(), 67 | // 'minutes': os.uptime() / 60, 68 | // 'hours': os.uptime() / 60 / 60, 69 | // 'days': os.uptime() / 60 / 60 / 60, 70 | // }) 71 | 72 | // Example-15 73 | // console.log(os.userInfo()); 74 | -------------------------------------------------------------------------------- /lessons/lesson49/code/inquirer/cli.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import inquirer from "inquirer"; 3 | 4 | import * as node from "./configs/tsconfig.node.json"; 5 | import * as react from "./configs/tsconfig.react.json"; 6 | import * as recommended from "./configs/tsconfig.recommended.json"; 7 | 8 | const prompt = inquirer.createPromptModule(); 9 | 10 | prompt([ 11 | { 12 | type: "confirm", 13 | message: `Your current working directory is ${process.cwd()} do you want to proceed?`, 14 | name: "proceed", 15 | default: true, 16 | }, 17 | ]) 18 | .then(({ proceed }) => { 19 | if (!proceed) { 20 | console.log("\x1b[33m%s", "Bye!"); 21 | 22 | process.exit(0); 23 | } 24 | 25 | prompt([ 26 | { 27 | type: "list", 28 | message: "Pick the typescript configuration file for your needs:", 29 | name: "config", 30 | choices: ["recommended", "react", "node"], 31 | }, 32 | { 33 | type: "input", 34 | message: 35 | "Enter the path to the directory you want the configuration file to be copied to:\n", 36 | name: "path", 37 | default: process.cwd(), 38 | validate: async (value) => { 39 | try { 40 | const stats = await fs.promises.stat(value); 41 | 42 | if (!stats.isDirectory()) { 43 | return "Not a directory!"; 44 | } 45 | } catch (e) { 46 | return e; 47 | } 48 | 49 | return true; 50 | }, 51 | }, 52 | ]).then(({ path, config }) => { 53 | let fileToWrite = ""; 54 | if (config === "node") { 55 | fileToWrite = JSON.stringify(node); 56 | } else if (config === "react") { 57 | fileToWrite = JSON.stringify(react); 58 | } else { 59 | fileToWrite = JSON.stringify(recommended); 60 | } 61 | 62 | fs.writeFile(path + "\\tsconfig.json", fileToWrite, () => { 63 | console.log("\x1b[32m%s\x1b[0m", "Config file succesfully copied!"); 64 | }); 65 | }); 66 | }) 67 | .catch((error) => { 68 | if (error.isTtyError) { 69 | console.log("Prompt couldn't be rendered in the current environment"); 70 | } else { 71 | console.log("Something else went wrong"); 72 | } 73 | }); 74 | -------------------------------------------------------------------------------- /lessons/lesson49/code/inquirer/configs/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Node 16", 4 | 5 | "compilerOptions": { 6 | "lib": ["es2021"], 7 | "module": "commonjs", 8 | "target": "es2021", 9 | 10 | "strict": true, 11 | "esModuleInterop": true, 12 | "skipLibCheck": true, 13 | "forceConsistentCasingInFileNames": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lessons/lesson49/code/inquirer/configs/tsconfig.react.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Create React App", 4 | 5 | "compilerOptions": { 6 | "lib": ["dom", "dom.iterable", "esnext"], 7 | "module": "esnext", 8 | "target": "es5", 9 | 10 | "allowJs": true, 11 | "allowSyntheticDefaultImports": true, 12 | "esModuleInterop": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "isolatedModules": true, 15 | "jsx": "react-jsx", 16 | "moduleResolution": "node", 17 | "noEmit": true, 18 | "noFallthroughCasesInSwitch": true, 19 | "resolveJsonModule": true, 20 | "skipLibCheck": true, 21 | "strict": true 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lessons/lesson49/code/inquirer/configs/tsconfig.recommended.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2015", 4 | "module": "commonjs", 5 | "strict": true, 6 | "esModuleInterop": true, 7 | "skipLibCheck": true, 8 | "forceConsistentCasingInFileNames": true 9 | }, 10 | "$schema": "https://json.schemastore.org/tsconfig", 11 | "display": "Recommended" 12 | } 13 | -------------------------------------------------------------------------------- /lessons/lesson49/code/inquirer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "inquirer", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "ts-node cli.ts", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "inquirer": "^8.1.2" 15 | }, 16 | "devDependencies": { 17 | "@types/inquirer": "^7.3.3" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lessons/lesson49/code/inquirer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noEmit": true, 4 | "module": "CommonJS", 5 | "target": "ES2020", 6 | "esModuleInterop": true, 7 | "resolveJsonModule": true 8 | }, 9 | "include": ["*.ts"], 10 | "exclude": ["node_modules"] 11 | } 12 | -------------------------------------------------------------------------------- /lessons/lesson49/images/arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson49/images/arch.png -------------------------------------------------------------------------------- /lessons/lesson49/images/nodejs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson49/images/nodejs.png -------------------------------------------------------------------------------- /lessons/lesson50/homework.md: -------------------------------------------------------------------------------- 1 | --- 2 | Зачем: Создаете многопользовательское приложение (уже сложнее, чем просто чат), включая серверную часть. Учитесь разворачивать приложение так, чтобы оно было доступно в сети. Тренируете навык работы с документацией незнакомых инструментов. Собираете вместе то, что вы изучили за время курса. 3 | --- 4 | 5 | ### "Создать приложение-игру крестики-нолики с поддержкой многопользовательской игры" 6 | 7 | В результате выполнения ДЗ вы создадите базовый WS-сервер, с поддержкой подключения клиентов, узнаете как общаться через WS и как настраивать выполнение серверного javascript. Потренируете навыки разработки в контексте многопользовательской работы и используете redux не только на клиенте 8 | 9 | #### Необходимо: 10 | 11 | - создать и настроить проект\* 12 | - создать приложение сервер, которое поддерживает подключение 2 пользователей на игру, и любого числа пользователей на просмотр 13 | - создать клиентское приложение, которое может работать с сервером, отображать ход игры и позволяет активным игрокам делать ходы 14 | - обработать ситуации, когда зрители пытаются сделать ход, или активный игрок ходит вне своей очереди 15 | - опубликовать работу с использованием любого сервиса публикации (например, Netlify или Render) 16 | - подготовить работу с сдаче\* 17 | - сделать ревью 2 других работ 18 | - сбросить ссылку на PR, опубликованный проект и рассмотренные пуллреквесты в част с преподавателем 19 | 20 | #### Критерии оценки: 21 | 22 | - создано клиентское приложение, которое отображает ход игры - **1** балл 23 | - клиентское приложение поддерживает ввод ходов - **1** балл 24 | - клиентское приложение позволяет менять статус активный игрок/зритель - **1** балл 25 | - серверное приложение использует redux для управления логикой - **1** балл 26 | - серверное приложение обрабатывает и публикует ходы - **1** балл 27 | - серверное приложение корректно обрабатывает нестандартные ситуации - **1** балл 28 | - проверены 2 работы - **1** балл 29 | 30 | #### Задание не проверяется при не соответствии базовым требованиям к заданию! 31 | 32 | #### Принято - от 5 баллов 33 | -------------------------------------------------------------------------------- /lessons/lesson50/images/bob_martin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson50/images/bob_martin.jpg -------------------------------------------------------------------------------- /lessons/lesson50/images/exorcism.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson50/images/exorcism.gif -------------------------------------------------------------------------------- /lessons/lesson50/images/jsguy.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson50/images/jsguy.gif -------------------------------------------------------------------------------- /lessons/lesson50/images/polnomochiya.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson50/images/polnomochiya.jpg -------------------------------------------------------------------------------- /lessons/lesson50/images/ron_jeffries.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson50/images/ron_jeffries.jpg -------------------------------------------------------------------------------- /lessons/lesson51/images/http.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson51/images/http.png -------------------------------------------------------------------------------- /lessons/lesson51/images/middleware.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson51/images/middleware.jpg -------------------------------------------------------------------------------- /lessons/lesson51/images/osi.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvscode/otus--javascript-basic/0f77a72f0d224aacf21bb93975f7a0b5e19c1f53/lessons/lesson51/images/osi.jpeg -------------------------------------------------------------------------------- /lessons/lesson52/code/ls.js: -------------------------------------------------------------------------------- 1 | // import fs from "fs"; 2 | // const logger = console.log; 3 | // const [directory, pattern] = process.argv.slice(2); 4 | 5 | // if (["--help", "-h"].includes(directory)) { 6 | // logger("Usage: node ls.mjs [pattern]"); 7 | // process.exit(0); 8 | // } 9 | 10 | // fs.readdir(directory, (err, files) => { 11 | // const regex = new RegExp(pattern, "g"); 12 | // files.forEach((file) => { 13 | // if (!pattern || regex.test(file)) { 14 | // logger(file); 15 | // } 16 | // }); 17 | // }); 18 | -------------------------------------------------------------------------------- /lessons/lesson52/code/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Sample node.js program", 3 | "private": true, 4 | "version": "0.1.0", 5 | "scripts": { 6 | "start": "node index.js", 7 | "dev": "nodemon index.js" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lessons/lesson52/code/practice/ls.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require("fs"); 4 | 5 | const args = process.argv.slice(2); 6 | const logger = console.log; 7 | 8 | /** 9 | * Prints help message and aborts program 10 | */ 11 | function help() { 12 | logger("Usage: node ls.js [filter-regex]"); 13 | process.exit(0); 14 | } 15 | 16 | /** 17 | * Prints list of files in a directory 18 | * @param {string} dir 19 | * @param {string} pattern 20 | */ 21 | function listFiles(dir, pattern) { 22 | fs.readdir(directory, (err, files) => { 23 | const regex = new RegExp(pattern, "g"); 24 | files.forEach((file) => { 25 | if (!pattern || regex.test(file)) { 26 | logger(file); 27 | } 28 | }); 29 | }); 30 | } 31 | 32 | if (["--help", "-h"].includes(args[0])) { 33 | help(); 34 | } 35 | 36 | const [directory, pattern] = args; 37 | 38 | listFiles(directory, pattern); 39 | 40 | /** 41 | * Example: ./ls.js . '.+json' 42 | * Example: ./ls.js . '.+js$' 43 | */ 44 | -------------------------------------------------------------------------------- /lessons/lesson52/code/practice/server.js: -------------------------------------------------------------------------------- 1 | const http = require("http"); 2 | const url = require("url"); 3 | 4 | function getTime(timeZone) { 5 | return new Date().toLocaleString("en-US", { timeZone }); 6 | } 7 | 8 | // Create a server object 9 | const server = http.createServer((req, res) => { 10 | const parts = url.parse(req.url, true); 11 | const query = parts.query; 12 | res.write(`Current date is "${getTime(query.timezone)}"`); // Write a response to the client 13 | res.end(); // End the response 14 | }); 15 | 16 | server.listen(8080); 17 | 18 | /** 19 | * Example: http://localhost:8080 20 | * Example: http://localhost:8080?timezone=Asia/Jakarta 21 | * Example: http://localhost:8080?timezone=America/New_York 22 | */ 23 | -------------------------------------------------------------------------------- /lessons/lesson52/code/sample.js: -------------------------------------------------------------------------------- 1 | const { createServer } = require("http"); 2 | const fs = require("fs"); 3 | 4 | // Create a server object 5 | const server = createServer((req, res) => { 6 | res.write("Hello World ??"); // Write a response to the client 7 | res.end(); // End the response 8 | }); 9 | 10 | server.listen(8080); 11 | -------------------------------------------------------------------------------- /lessons/lesson52/hw1.md: -------------------------------------------------------------------------------- 1 | --- 2 | Зачем: Тренируемся писать консольные приложения и публиковать их в Github (вы уже делали это с API, но инструменты командной строки немного отличаются). Учимся работать с файловой системой и запуском команд из nodejs. 3 | --- 4 | 5 | **Необходимо:** 6 | 7 | - создать и настроить проект 8 | 9 | - реализовать скрипт командной строки, который поддерживает ввод параметров 10 | -- через аргументы 11 | -- через интерфейс с запросами в консоли 12 | 13 | - позволяет опубликовать проект (собрать его при необходимости) на github pages с заданными параметрами 14 | - команда для сборки (если нужно) 15 | - директория для публикации 16 | - репозиторий для публикации 17 | - настройки доступа к репозиторию 18 | 19 | - опубликовать, чтобы можно было использовать через npx 20 | - оформить README с подробным описанием и gif с демонстрацией работы, списком команд для использования 21 | 22 | - подготовить работу с сдаче 23 | - сделать ревью 2 других работ 24 | 25 | В чат с преподавателем сбросить ссылку на репозиторий и на опубликованный пакет. Задание принято если выполнены все пункты: 26 | 27 | - создан пакет, реализующий нужный функционал 28 | - открыт PR 29 | - код прошел линтинг (и тесты) 30 | - проект опубликован на npmjs 31 | - пакет работает без установки (через npx) 32 | - вместе со ссылками сброшены ссылки на 2 PR других студентов, на которые сделано ревью (если ссылки не удается найти в чате, обратиться к преподавателям) 33 | -------------------------------------------------------------------------------- /lessons/lesson52/hw2.md: -------------------------------------------------------------------------------- 1 | **Задание:** 2 | 3 | Привести в порядок репозитории с заданиями курса: 4 | 5 | - Добавить описание README, которое должно содержать 6 | - описание проекта (что, зачем) 7 | - инструкция по локальному запуску (как склонировать, установить и запустить) 8 | - ссылки на задеплоенное приложение или на codesandbox 9 | - бэдж со статусом workflow по проверке линтерами и тестами 10 | 11 | В чат с преподавателем сбросить ссылки на репозитории. Задание принято от 4х репозиториев. 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "otus--javascript-basic", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "dev": "reveal-md", 9 | "lint:prettier": "prettier --check .", 10 | "lint:spell": "yaspeller .", 11 | "lint": "yarn lint:prettier && yarn lint:code", 12 | "lint:code": "eslint --ext .js,.ts,.md .", 13 | "prepare": "husky install" 14 | }, 15 | "keywords": [], 16 | "author": "", 17 | "license": "ISC", 18 | "devDependencies": { 19 | "@typescript-eslint/eslint-plugin": "^5.40.1", 20 | "@typescript-eslint/parser": "^5.40.1", 21 | "eslint": "^8.26.0", 22 | "eslint-plugin-markdown": "^3.0.0", 23 | "eslint-plugin-react": "^7.31.10", 24 | "husky": "^8.0.1", 25 | "lint-staged": "^13.0.3", 26 | "prettier": "^2.7.1", 27 | "reveal-md": "^5.3.4", 28 | "typescript": "^4.8.4", 29 | "yaspeller": "^9.1.0" 30 | }, 31 | "lint-staged": { 32 | "*.{js,json,html,css,md,yml,yaml,ts,tsx}": "prettier --write", 33 | "{*js,*.md}": "eslint --cache --fix", 34 | "*.md": "echo 'yaspeller is disabled'" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /plugins.js: -------------------------------------------------------------------------------- 1 | options.dependencies = options.dependencies || []; 2 | 3 | options.dependencies.push( 4 | // https://github.com/naamor/reveal.js-tableofcontents 5 | { 6 | src: window.location.href.includes("vvscode") 7 | ? "../_assets/plugins/tableofcontents.js" 8 | : "_assets/plugins/tableofcontents.js", 9 | } 10 | ); 11 | -------------------------------------------------------------------------------- /preparation/tasks.md: -------------------------------------------------------------------------------- 1 | ## Линейные алгоритмы 2 | 3 | 1. В переменных a и b хранятся числа. Написать программу, которая выводит в консоль произведение и сумму этих чисел. 4 | 5 | 2. В двух переменных хранятся строки символов. Написать программу, которая выведет в консоль суммарное количество символов в обоих строках. 6 | 7 | 3. Написать программу, которая запрашивает у пользователя ввод трёхзначного числа, а потом выводит в консоль сумму цифр введённого числа. 8 | 9 | ## Условия 10 | 11 | 1. В переменных a и b хранятся числа. Вывести в консоль наибольшее из них. 12 | 13 | 2. Запросить у пользователя ввод числа от 1 до 12. Вывести в консоль название месяца, соответствующее этому числу (1 — январь, 2 — февраль и т.д.). 14 | 15 | 3. В переменных circle и square хранятся площади круга и квадрата соответственно. Написать программу, которая определяет, поместится ли круг в квадрат. 16 | 17 | ## Циклы 18 | 19 | 1. Вывести в консоль сумму всех целых чисел от 50 до 100. 20 | 21 | 2. Вывести в консоль таблицу умножения на 7. 22 | 23 | ``` 24 | 7 x 1 = 7 25 | 7 x 2 = 14 26 | … 27 | 7 x 9 = 63 28 | ``` 29 | 30 | 3. Запросить у пользователя ввод числа N. Вывести в консоль среднее арифметическое всех нечётных чисел от 1 до N. 31 | 32 | ## Объекты 33 | 34 | Создайте объект user, содержащий поле name со 35 | значением ‘John’. 36 | 37 | 1. Запросить у пользователя ввод числа. Записать введенное значение в поле age объекта user. 38 | 39 | 2. Создать копию объекта user с именем admin. Добавить новому объекту поле role со значением ‘admin’. 40 | 41 | 3. Записать все значения полей объекта admin в 42 | отдельные переменные. Имена переменных 43 | должны совпадать с названиями полей 44 | 45 | ## Массивы 46 | 47 | Создайте массив целых чисел из 10 элементов. 48 | 49 | 1. Выведите в консоль сумму всех элементов массива. 50 | 51 | 2. Создайте новый массив на основе исходного, в котором каждый элемент будет вдвое больше элемента исходного массива с таким же индексом. (a[1] = 3, b[1] = 6, где a — исходный массив, b — новый массив). 52 | 53 | 3. Найдите и выведите в консоль наибольший и 54 | наименьший элементы исходного массива. 55 | 56 | ## Функции 57 | 58 | 1. Напишите функцию diff, которая получает в качестве 59 | параметров 2 числа и возвращает разницу между 60 | наибольшим и наименьшим. 61 | 62 | 2. Напишите функцию isWord, которая принимает на 63 | вход текстовую строку. Функция возвращает true, если 64 | строка состоит из одного слова и false, если из 65 | нескольких. 66 | 67 | 3. Напишите функцию pow(a, x), которая вернёт 68 | значение числа a, возведённого в степень x. 69 | -------------------------------------------------------------------------------- /reveal-md.json: -------------------------------------------------------------------------------- 1 | { 2 | "separator": "", 3 | "verticalSeparator": "", 4 | "theme": "white", 5 | "watch": true, 6 | "css": ".reveal-md/styles.css", 7 | "listingTemplate": ".reveal-md/listing.html", 8 | "scripts": ["plugins.js"] 9 | } 10 | -------------------------------------------------------------------------------- /reveal.json: -------------------------------------------------------------------------------- 1 | { 2 | "slideNumber": true, 3 | "hash": true, 4 | "history": false, 5 | "backgroundTransition": "fade", 6 | "width": "80%", 7 | "tableofcontents": { 8 | "title": "План", 9 | "position": 1000 10 | } 11 | } 12 | --------------------------------------------------------------------------------